1 Description

This R notebook details the data processing and visualization for growth competition experiments with a CRISPRi sgRNA library. The library contains around 20,000 unique sgRNA repression mutants tailored for the cyanobacterium Synechocystis sp. PCC6803. This library is the second version (therefore “V2”) of an sgRNA library for Synechocystis, containing five instead of only two sgRNAs per gene. In some cases, genes or ncRNAs are so short that it is not possible to design a maximum of five individual sgRNAs.

The first iteration of the Synechocystis sgRNA library was published in Nature Communications, 2020.

2 Prerequisites

Load required packages.

suppressPackageStartupMessages({
  library(tidyverse)
  library(ggrepel)
  library(lattice)
  library(latticeExtra)
  library(latticetools)
  library(scales)
  library(dendextend)
  library(vegan)
  library(tsne)
  library(KEGGREST)
  library(limma)
  library(corrplot)
  library(kableExtra)
  library(grid)
  library(ggpubr)
})

Define global figure style, default colors, and a plot saving function.

3 Quality control

3.1 Data import

Load raw data. The main table contains already normalized quantification of all sgRNAs, fold change, multiple hypothesis corrected p-values, and fitness scores on the guide RNA and gene level. Contrary to the processing of our first CRISPRi library V1, much of the functionality from the notebook was transferred into a new NExtflow CRISPRi library pipeline available on github.

load("../data/input/result.Rdata")
df_main <- DESeq_result_table
rm(DESeq_result_table)

3.2 Data annotation

Different annotation columns are added to the main data frame, including a short sgRNA identifier (excluding the position on the gene), an sgRNA index (1 to 5), and genome annotation from Uniprot. The Uniprot data is dynamically downloaded for every update of this pipeline using their very simple API (read_tsv("https://www.uniprot.org/uniprot/?query=taxonomy:1111708&format=tab")). The full list of columns that can be queried is available here. Pathway annotation from KEGG is later in the pipeline added using the KEGGREST package.

df_main <- df_main %>%
  group_by(sgRNA_target) %>%
  mutate(sgRNA_type = if_else(grepl("^nc_", sgRNA), "ncRNA", "gene")) %>%
  ungroup %>%
  
  # map trivial names to LocusTags using a manually curated list
  left_join(
    read_tsv("../data/input/mapping_trivial_names.tsv", col_types = cols()),
    by = c("sgRNA_target" = "gene")) %>%

  # split condition into separate cols
  separate(condition, into = c("carbon", "light", "treatment_1", "treatment_2"),
    sep = ", ", remove = FALSE, fill = "right") %>%
  unite("treatment", treatment_1, treatment_2, sep = ", ", na.rm = TRUE)

Overview about the different conditions.

df_cultivation_summary <- df_main %>% group_by(condition) %>%
  summarize(
    time_points = paste(unique(time), collapse = ", "),
    carbon = unique(carbon),
    light = unique(light),
    treatment = unique(treatment),
    min_fit = min(fitness),
    med_fit = median(fitness),
    max_fit = max(fitness))

print(df_cultivation_summary)
write_csv(df_cultivation_summary, file = "../data/output/cultivation_summary.csv")

Retrieve gene info from uniprot and merge with main data frame. We need to make a custom function to retrieve and parse the data from uniprot, because of a bug in the security level on Ubuntu 20.04. The fallback option is to load a local copy of uniprot annotation for this organism.

library(httr)
uniprot_url <- paste0(
   "https://www.uniprot.org/uniprot/?query=taxonomy:1111708&format=tab&",
   "columns=id,genes,genes(PREFERRED),protein_names,length,mass,ec,database(KEGG)")

get_uniprot <- function(url) {
  # reset security level, caused by a faulty SSL certificate on server side,
  # see this thread: https://github.com/Ensembl/ensembl-rest/issues/427
  httr_config <- config(ssl_cipher_list = "DEFAULT@SECLEVEL=1")
  res <- with_config(config = httr_config, GET(url))
  server_error = simpleError("")
  df_uniprot <- tryCatch(
    read_tsv(content(res), col_types = cols()),
    error = function(server_error) {
      message("Uniprot server not available, falling back on local Uniprot DB copy")
      read_tsv("../data/input/uniprot_synechocystis.tsv", col_types = cols())
    }
  )
}

df_uniprot <- get_uniprot(uniprot_url) %>%
  rename_with(tolower) %>%
  rename(locus = `cross-reference (kegg)`, gene_name = `gene names`,
    gene_name_short = `gene names  (primary )`, ec_number = `ec number`,
    protein = `protein names`, uniprot_ID = entry
  ) %>%
  separate_rows(locus, sep = ";syn:") %>%
  mutate(locus = str_remove_all(locus, "syn:|;")) %>%
  filter(!is.na(locus))

df_main <- left_join(df_main, filter(df_uniprot, !duplicated(locus)),
  by = "locus")

3.3 Number of sgRNAs

Each gene is represented by up to five sgRNAs. We can test if all or only some of the 5 sgRNAs are “behaving” in the same way in the same conditions, more mathematically speaking we can estimate the correlation of every sgRNA with another. First let’s summarize how many genes have 5, 4, 3 sgRNAs and so on associated with them.

# N unique sgRNAs in dataset
paste0("Number of unique sgRNAs: ", unique(df_main$sgRNA) %>% length)
[1] "Number of unique sgRNAs: 21470"
# N genes with 1,2,3,4 or 5 sgRNAs
plot_sgRNAs_per_gene <- df_main %>%
  group_by(sgRNA_type, sgRNA_target) %>%
  summarize(n_sgRNAs = length(unique(sgRNA_position)), .groups = "drop_last") %>%
  count(n_sgRNAs) %>% filter(n_sgRNAs <= 5) %>%
  ggplot(aes(x = factor(n_sgRNAs, 5:1), y = n, label = n)) +
  geom_col(show.legend = FALSE) +
  geom_text(size = 3, nudge_y = 200, color = grey(0.5)) +
  facet_grid(~ sgRNA_type) +
  labs(x = "sgRNAs / target", y = "targets") +
  coord_cartesian(ylim = c(-50, 3500)) +
  custom_theme()

print(plot_sgRNAs_per_gene)

3.4 Fitness distribution of all conditions

Before biological analysis continues, we need to check if fitness (and log2 FC from which it is calculated) is equally distributed. For example, strictly essential genes like ribosomal genes should show the same degreee of depletion over time, regardless of condition.

We can compare fitness over all conditions using a scatter plot matrix. We can see that some conditions are very similar to each other, for example the conditions treated with glucose (LC, LL +g, LC, LL, +D, +G, HC, LL +g). Others are more dissimilar to the rest, for example LC, IL and LC, LL, +FL. They are more alike each other, although LC, LL, +FL should be more comparable to LC, LL, hinting at experimental bias. In this case both of these conditions (and LC, LL, +G) were pre-cultivated in low light instead of high light, as opposed to the rest of the samples.

df_main %>% filter(time == 0, sgRNA_index == 1) %>%
  select(locus, condition, fitness) %>%
  filter(!is.na(locus)) %>%
  pivot_wider(names_from = condition, values_from = fitness) %>%
  select(-locus) %>%
  custom_splom(pch = 19, cex = 0.3, col = grey(0.4, 0.4), pscales = 0)

Another way to look at the result of the normalization is to compare the global distribution of log2 FC values, as a density plot.

library(ggridges)
df_main %>% filter(time == 10) %>%
  select(sgRNA, condition, log2FoldChange) %>%
  distinct %>%
  ggplot(aes(x = log2FoldChange, y = condition, group = condition)) + 
  geom_density_ridges(fill = "#00AFBB99", col = grey(0.4)) +
  lims(x = c(-2, 1.5)) +
  custom_theme()
Picking joint bandwidth of 0.0309

4 Gene fitness

SgRNA fitness score was calculated as the AUC of log2FC read number over generation time, normalized by generation time. Gene fitness score was calculated as the weighted mean of individual sgRNA fitness scores. The weights are made up of two different components,

  1. guide RNA correlation
  2. guide RNA efficiency

4.1 Guide RNA correlation

A correlation score was calculated by computing the correlation coefficient of all sgRNAs to each other. This score is robustly summarized by taking the median, and rescaling it from the respective minima and maxima [-1, 1] to [0, 1]. This score served as a weight component for each sgRNA to calculate the (global) weighted mean of log2 FC and fitness over all sgRNAs. The score has the characteristic that it gives a weight of 1 for an sgRNA perfectly correlated with all other sgRNAs of the same gene, and a weight of 0 for sgRNAs perfectly anti-correlated to the other sgRNAs.

For a matrix of \(x = 1 .. m\) sgRNAs and \(y = 1 .. n\) observations (measurements), the correlation \(R\) of one sgRNA to another is calculated using Pearson’s method:

\(R_x=cor([log_2FC_{x1,y1} ... log_2FC_{x1,yn}], [log_2FC_{x2,y1} ... log_2FC_{x2,yn}])\)

The correlation weight of one sgRNA is then calculated as median of all \(R\) rescaled between 0 and 1.

\(w_x = \frac{1 + median(R_1, R_2, ..., R_m)}{2}\)

The following example shows the correlation matrix for the 5 rps10 sgRNAs, and their weights. The self correlation of each sgRNA (R = 1) is removed prior to weight determination.

cor_matrix <- df_main %>% filter(sgRNA_target == "rps10") %>% ungroup %>%
  select(sgRNA_index, log2FoldChange, condition, time) %>%
  pivot_wider(names_from = c("condition", "time"), values_from = log2FoldChange) %>%
  arrange(sgRNA_index) %>% column_to_rownames("sgRNA_index") %>%
  as.matrix %>% t %>% cor(method = "pearson")

weights <- cor_matrix %>% replace(., . == 1, NA) %>%
  apply(2, function(x) median(x, na.rm = TRUE)) %>%
  rescale(from = c(-1, 1), to = c(0, 1))

# plot heatmap
lattice::levelplot(cor_matrix %>% replace(., . == 1, NA),
  col.regions = custom_range(20))


# print weights
weights
        1         2         3         4         5 
0.8490072 0.7894025 0.4345219 0.8341260 0.7745213 

4.2 Guide RNA efficiency

The correlation of each sgRNA with each other is a “global” parameter as it is identical over all conditions. A second global parameter, sgRNA efficiency, was obtained using a similar approach. We expect that fitness of all sgRNAs for one gene is not normally distributed because sgRNAs are not ideal replicate measurements. They are biased by position effects and off-target binding, see Wang et al., Nature Comms, 2018 for a very insightful and comprehensive analysis of the number and position of sgRNAs required to estimate gene fitness.

Here, sgRNA efficiency \(E\) was calculated as the median absolute fitness (AUC of log2FC over time) of an sgRNA \(x = 1 .. m\) over all observations [conditions] \(y = 1 .. n\).

\(E_x=median(abs(fitness_{x1, y1}, fitness_{x1, y2}, ..., fitness_{x1, yn}))\)

To normalize between all sgRNAs, \(E\) is rescaled to a range between 0 and 1.

\(E_x=\frac{E_x}{max(E_1, E_2, ..., E_m)}\)

This is the resulting sgRNA efficiency for the example gene above, rps10.

df_main %>% filter(sgRNA_target == "rps10") %>% ungroup %>%
  select(sgRNA_index, sgRNA_efficiency) %>% distinct %>% 
  arrange(sgRNA_index) %>% deframe
         1          2          3          4          5 
1.00000000 0.15916697 0.03206224 0.19239830 0.51606896 

4.3 Position bias of sgRNA repression

Plot the weight of each sgRNA to see if there is a dependency between correlation and sgRNA position. There is no significant trend.

We can also quantify how many genes have strongly correlated sgRNAs and how many have outliers. In order to do this, the median weight of the (up to) 5 sgRNAs per gene is plotted. Generally, the median weight ranges between 0.5 and 1.0, showing on average good correlation.

plot_sgRNA_correlation <- df_main %>%
  select(sgRNA_target, sgRNA_index, sgRNA_correlation, sgRNA_type) %>%
  filter(sgRNA_index <= 5, sgRNA_type == "gene") %>%
  distinct %>%
  # plot
  ggplot(aes(x = factor(sgRNA_index), y = sgRNA_correlation)) +
  geom_boxplot(outlier.shape = "") +
  labs(x = "sgRNA position (relative)", y = "correlation") +
  stat_summary(fun.data = function(x) c(y = median(x)+0.07, 
    label = round(median(x), 2)), geom = "text", size = 3) +
  stat_summary(fun.data = function(x) c(y = 1.1, 
    label = length(x)), geom = "text", color = grey(0.5), size = 3) +
  coord_cartesian(ylim = c(-0.15, 1.15)) +
  custom_theme()

plot_sgRNA_correlation_hist <- df_main %>%
  select(sgRNA_target, sgRNA_index, sgRNA_correlation, sgRNA_type) %>%
  filter(sgRNA_index <= 5, sgRNA_type == "gene") %>%
  distinct %>% group_by(sgRNA_target) %>%
  summarize(
    median_sgRNA_correlation = median(sgRNA_correlation),
    min_sgRNA_correlation = min(sgRNA_correlation)
  ) %>%
  # plot
  ggplot(aes(x = median_sgRNA_correlation)) +
  geom_histogram(bins = 40, fill = custom_colors[1], alpha = 0.7) +
  custom_theme()

ggarrange(plot_sgRNA_correlation, plot_sgRNA_correlation_hist, ncol = 2)

Second, the binding position of the sgRNAs could be correlated to the strength of repression. In other words sgRNAs binding closer to the promoter could have stronger ability to repress a gene, see Figure 1 B in Wang et al., Nature Comms, 2018. We plot sgRNA efficiency for genes only, because the absolute majority of those has 5 sgRNAs.

plot_sgRNA_efficiency <- df_main %>%
  filter(sgRNA_index <= 5, sgRNA_type == "gene") %>%
  select(sgRNA_target, sgRNA_index, sgRNA_efficiency) %>% distinct %>%
  ggplot(aes(x = factor(sgRNA_index), y = sgRNA_efficiency)) +
  geom_boxplot(notch = FALSE, outlier.shape = ".") +
  labs(x = "sgRNA position (relative)", y = "repression efficiency") +
  coord_cartesian(ylim = c(-0.15, 1.15)) +
  stat_summary(fun.data = function(x) c(y = median(x)+0.07, 
    label = round(median(x), 2)), geom = "text", size = 3) +
  stat_summary(fun.data = function(x) c(y = 1.1, 
    label = length(x)), geom = "text", color = grey(0.5), size = 3) +
  custom_theme()


plot_sgRNA_efficiency_hist <- df_main %>%
  filter(sgRNA_index <= 5, sgRNA_type == "gene") %>%
  select(sgRNA_target, sgRNA_position, sgRNA_efficiency) %>% distinct %>%
  group_by(sgRNA_position) %>%
  summarize(sgRNA_efficiency = median(sgRNA_efficiency), n_pos = n()) %>%
  filter(n_pos >= 10) %>%
  ggplot(aes(x = sgRNA_position, y = sgRNA_efficiency)) +
  labs(x = "sgRNA position (nt)", y = "repression efficiency") +
  geom_point(col = alpha(custom_colors[5], 0.5)) +
  geom_smooth() +
  custom_theme()

ggarrange(plot_sgRNA_efficiency, plot_sgRNA_efficiency_hist, ncol = 2)
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

Export draft Figure 1 for manuscript.

plot_selected_sgRNAs <- df_main %>%
  filter(
    grepl("ctrl[1-5]$|rps10$", sgRNA_target), 
    condition %in% c("HC, HL", "HC, LL", "LC, IL", "LC, LL")) %>%
  mutate(
    sgRNA_index2 = as.numeric(str_extract(sgRNA_target, "[1-9]$")),
    sgRNA_index = case_when(sgRNA_position == 0 ~ sgRNA_index2, TRUE ~ sgRNA_index),
    sgRNA_target = str_extract(sgRNA_target, "[a-zA-Z]*")
  ) %>%
  ggplot(aes(x = time, y = log2FoldChange, color = factor(sgRNA_index))) +
  geom_line(size = 1) + geom_point(size = 2) +
  facet_grid(sgRNA_target ~ condition) +
  custom_theme(legend.position = 0) +
  coord_cartesian(ylim = c(-4.5, 2.5)) +
  scale_x_continuous(breaks = c(0,2,4,6,8,10)) +
  scale_color_manual(values = custom_range(5))

svg(filename = "../figures/figure1.svg", width = 7, height = 5.5)
ggarrange(ncol = 2, nrow = 2, widths = c(0.6, 0.4), labels = c("A", "C", "B", "D"), font.label = list_fontpars,
  plot_sgRNAs_per_gene + theme(plot.margin = unit(c(12,12,12,12), "points")),
  plot_sgRNA_efficiency + theme(plot.margin = unit(c(26,12,12,12), "points")),
  plot_selected_sgRNAs + theme(plot.margin = unit(c(12,-4,12,14), "points")),
  plot_sgRNA_correlation + theme(plot.margin = unit(c(26,12,12,12), "points"))
)
dev.off()
null device 
          1 

Export supplemental figure with all ribosomal genes (rpsNN/rplNN).

plot_sgRNAs_ribosome <- df_main %>%
  filter(str_detect(sgRNA_target, "rp[sl][0-9]*$")) %>%
  filter(condition == "LC, LL") %>%
  ggplot(aes(x = time, y = log2FoldChange, color = factor(sgRNA_index))) +
  geom_line(size = 1) + geom_point(size = 2) +
  facet_wrap(~ sgRNA_target, ncol = 7) +
  custom_theme(legend.position = "top") +
  scale_color_manual(values = custom_range(5))

print(plot_sgRNAs_ribosome)

We generate a reduced table focusing on gene fitness insteas of sgRNA fitness. The data set already contains guide RNA level p-values from DESeq2 (pval, padj), and gene-based p-values from from Wilcoxon Rank Sum test (p_fitness, p_fitness_adj). Since statistical significance was tested for many genes in parallel, the second columns of p-values represents multiple-hypothesis corrected p-values. The Benjamini-Hochberg method was used for this purpose. The pipeline also outputs a score that takes both effect size and p-value into account (comb_score), according to the publication from Wang et al., Nat Comm, 2018. This score is simply the absolute fitness score multiplied by the negative log10 p-value.

df_gene <- df_main %>%
  select(sgRNA_target, sgRNA_type, locus,
    gene_name, condition, 
    carbon, light, treatment, time,
    wmean_log2FoldChange, sd_log2FoldChange,
    wmean_fitness, sd_fitness,
    p_fitness_adj, comb_score
  ) %>% distinct

4.4 Global distribution of gene fitness

Global distribution of weighted mean fitness for all genes. Effect of ncRNA repression seems to be much lower than effect of gene repression.

plot_all_fitness_hist <- df_gene %>% filter(time == 0) %>%
  ggplot(aes(x = wmean_fitness, fill = sgRNA_type)) +
  geom_histogram(bins = 100) +
  coord_cartesian(xlim = c(-4, 4), ylim = c(0, 1000)) +
  facet_wrap( ~ condition, ncol = 4) +
  custom_theme() +
  scale_fill_manual(values = custom_colors[c(3:4)])

print(plot_all_fitness_hist)

4.5 Gene fitness vs significance

plot_all_fitness_volc <- df_gene %>% filter(time == 0,
    condition %in% c("HC, HL", "LC, LL")) %>%
  arrange(sgRNA_type) %>%
  ggplot(aes(x = wmean_fitness, y = -log10(p_fitness_adj), col = sgRNA_type)) +
  geom_point(alpha = 0.5, size = 0.5) +
  geom_line(data = data.frame(x = c(seq(-8, -0.5, 0.1), seq(0.5, 8, 0.1)),
    y = 4/c(seq(8, 0.5, -0.1), seq(0.5, 8, 0.1))),
    aes(x = x, y = y, shape = NULL, col = NULL), lty = 2) +
  coord_cartesian(xlim = c(-7, 7), ylim = c(0, 2.5)) +
  custom_theme(aspect = 1, legend.position = "left", legend.key.size = unit(0.4, "cm")) +
  facet_wrap(~ condition) +
  labs(x = "fitness", y = expression("-log"[10]*" p-value")) +
  scale_color_manual(values = custom_colors[3:4]) +
  scale_shape_manual(values=c(1, 19))

print(plot_all_fitness_volc)

4.6 Behavior of control sgRNAs

Ten sgRNAs were included in the library that have no gene-specific targets. The following plot shows that these negative controls do not have an effect on strain fitness, except probably 2 sgRNAs in one specific condition.

plot_controls_sgRNAs <- df_main %>% filter(grepl("ctrl", sgRNA_target)) %>%
  ggplot(aes(x = time, y = log2FoldChange, color = sgRNA_target)) +
  geom_line(size = 1) + geom_point(size = 2) + ylim(-5, 5) +
  facet_wrap(~ condition, ncol = 4) +
  custom_theme() +
  scale_color_manual(values = custom_range(10))

print(plot_controls_sgRNAs)

save_plot(plot_controls_sgRNAs, width = 6.5, height = 5.0)

5 Gene enrichment

To plot gene fitness for the enzymes of central carbon metabolism, we need a complete list of enzymes and the genes that they are mapped to. To list the different KEGG databases that can be queried, use listDatabases(). Gene-pathway mappings are obtained and merged with pathway names and gene/enzyme names.

# get mapping of pathways for each gene
df_kegg <- keggLink("pathway", "syn") %>%
  enframe(name = "locus", value = "kegg_pathway_id") %>%
  
  # get list of pathways with name/ID pairs
  left_join(by = "kegg_pathway_id",
    keggList("pathway", "syn") %>%
    enframe(name = "kegg_pathway_id", value = "kegg_pathway")
  ) %>%
  
  # get list of gene/enzyme names
  left_join(by = "locus",
    keggList("syn") %>%
    enframe(name = "locus", value = "kegg_gene") %>%
    mutate(kegg_gene_short = str_extract(kegg_gene, "^[a-zA-Z0-9]*;") %>% 
      str_remove(";"))
  ) %>%
  
  # trim useless prefixes
  mutate(
    locus = str_remove(locus, "syn:"),
    kegg_pathway_id = str_remove(kegg_pathway_id, "path:"),
    kegg_pathway = str_remove(kegg_pathway, " - Synechocystis sp. PCC 6803")
  )

head(df_kegg)

5.1 Fitness per pathway

Sometimes even small effects in fitness can be relevant if several genes of the same pathway (or iso-enzymes) are affected. A simple fitness threshold will not reveal those changes. In such cases a more nuanced approach can be taken, a gene set enrichment analysis (GSEA). Several packages exist to test if functionally related genes are enriched, depleted, or both at the same time / the same conditions.

Before we test for enrichment of associated pathways/GO terms, we can have a look at the general depletion/enrichment per KEGG pathway. The fitness distribution per pathway can be visualized using a violin- or scatter plot.

plot_median_fitness_kegg <- df_gene %>% filter(time == 0) %>%
  inner_join(df_kegg, by = "locus") %>%
  group_by(kegg_pathway, condition) %>%
  summarize(.groups = "drop",
    fitness = median(wmean_fitness),
    n_genes = n()
  ) %>% filter(n_genes >= 20) %>%
  mutate(kegg_pathway = paste0(str_sub(kegg_pathway, 1, 25), "..")) %>%
  mutate(kegg_pathway = fct_reorder(kegg_pathway, fitness, .desc = TRUE)) %>%
  
  ggplot(aes(x = fitness, y = kegg_pathway)) +
  geom_boxplot(outlier.shape = NULL, color = grey(0.5), fill = grey(0.9)) +
  geom_point(aes(color = condition)) +
  geom_vline(xintercept = 0, lty = 2, color = grey(0.5)) +
  labs(x = "median fitness", y = "") +
  custom_theme(legend.position = c(0.25, 0.25), legend.key.size = unit(0.4, "cm")) +
  scale_fill_manual(values = colorRampPalette(custom_colors[1:5])(11)) +
  scale_color_manual(values = colorRampPalette(custom_colors[1:5])(11))

print(plot_median_fitness_kegg)

Export draft Figure 2 for manuscript. We add photosystem I and II genes as examples for differential depletion. A heatmap.

plot_sgRNAs_ps1 <- df_gene %>%
  filter(str_detect(sgRNA_target, "psa[A-Z]*"), time == 0) %>%
  mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
  ggplot(aes(x = condition, y = fct_rev(sgRNA_target), fill = wmean_fitness)) +
  geom_tile() + custom_theme() +
  labs(title = "Photosystem I", x = "", y = "") +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
  scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
    limits = c(-4, 4))

plot_sgRNAs_ps2 <- df_gene %>%
  filter(str_detect(sgRNA_target, "psb[A-Z]*"), time == 0) %>%
  mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
  mutate(sgRNA_target = str_replace(sgRNA_target, "psb13", "psbW")) %>%
  ggplot(aes(x = condition, y = fct_rev(sgRNA_target), fill = wmean_fitness)) +
  geom_tile() + custom_theme() +
  labs(title = "Photosystem II", x = "", y = "") +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
  scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
    limits = c(-4, 4))

ggarrange(ncol = 2, plot_sgRNAs_ps1, plot_sgRNAs_ps2)

svg(filename = "../figures/figure2.svg", width = 8, height = 7)
ggarrange(ncol = 2, widths = c(0.65, 0.35),
  ggarrange(nrow = 2, heights =  c(0.34, 0.66), labels = LETTERS[1:2], font.label = list_fontpars,
    plot_all_fitness_volc + theme(plot.margin = unit(c(14,-8,14,40), "points")),
    plot_median_fitness_kegg + theme(plot.margin = unit(c(6,12,12,12), "points"))),
  ggarrange(nrow = 2, heights =  c(0.4, 0.6), labels = LETTERS[3:4], font.label = list_fontpars,
    plot_sgRNAs_ps1 + theme(plot.margin = unit(c(12,0,-14,0), "points")),
    plot_sgRNAs_ps2 + theme(plot.margin = unit(c(12,0,0,0), "points"))
  )
)
dev.off()
null device 
          1 

5.2 Gene enrichment analysis (KEGG)

We use the functions kegga for KEGG enrichment analysis and goana for GO term enrichment from the limma package. Both functions test for over or under-representation of genes associated with certain pathways or GO terms. The functions don’t take the strength of differential fitness into account (DF; the depletion/enrichment over time).

df_kegg_enrichment <- lapply(unique(df_gene$condition), function(cond) {
  df_gene %>% filter(
  sgRNA_type == "gene", time == 0,
  condition == cond) %>%
  
  # filter for differential fitness (DF) genes
  filter(!between(wmean_fitness, -2.0, 2.0), !is.na(locus)) %>%
  
  # perform KEGG enrichment
  pull(locus) %>% kegga(species.KEGG = "syn") %>%
  mutate(condition = cond)
}) %>% bind_rows

head(df_kegg_enrichment)

Now we visualize the pathways that are most enriched for DF genes. It turns out that ribosomal proteins are extremely depleted and therefore score high on the negative log10 p-value for pathway enrichment.

df_kegg_enrichment %>%
  rename(kegg_pathway = Pathway) %>%
  group_by(kegg_pathway) %>% filter(N >= 20) %>%
  select(kegg_pathway, condition, P.DE) %>%
  mutate(log10_p_value = -log10(P.DE), .keep = "unused") %>%
  mutate(kegg_pathway = paste0(str_sub(kegg_pathway, 1, 25), "..")) %>%
  
  # make correlation plot
  pivot_wider(names_from = condition, values_from = log10_p_value) %>%
  column_to_rownames(var = "kegg_pathway") %>% as.matrix %>%
  corrplot(is.corr = FALSE, tl.col = grey(0.5), tl.cex = 0.8,
    col = colorRampPalette(custom_colors[c(1,5,2)])(10), col.lim = c(0, 20))

6 Unsupervised clustering of genes

6.1 Cluster genes by similarity

We can devise a generalized tidyverse friendly function to cluster a name variable by a value, grouped by one or more grouping variables. For example, cluster genes (name) by fitness (value) over several conditions (groups). The output is a factor with re-ordered levels.

fct_cluster <- function(variable, group, value, method = "ward.D2") {
  df <- tibble(variable = variable, group = group, value = value)
  df <- pivot_wider(df, names_from = group, values_from = value)
  mat <- as.matrix(column_to_rownames(df, var = "variable"))
  cl <- hclust(dist(mat), method = method)
  ord <- order.dendrogram(as.dendrogram(cl))
  factor(variable, unique(variable)[ord])
}

Heat map of fitness for all genes and all conditions.

plot_heatmap_all <- df_gene %>% filter(time == 0, !is.na(locus)) %>%
  mutate(locus = fct_cluster(locus, condition, wmean_fitness)) %>%
  mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
  ggplot(aes(x = locus, y = condition, fill = wmean_fitness)) +
  geom_tile() + custom_theme(legend.pos = "right") +
  labs(x = paste0("genes (", length(unique(df_gene$locus)),")"), y = "") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank()) +
  scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
    limits = c(-4, 4))

print(plot_heatmap_all)

Now we can plot all genes, a subset with only significant genes, and a dendrogram for clustering. The result is hard to interpret. With some exceptions, most genes are grouped in broad unspecific clusters that do not reveal clear relationships between treatment variables and fitness outcome.

# prepare new df and plot heatmap
df_heatmap <- df_gene %>% filter(time == 0, !is.na(locus)) %>%
  group_by(locus) %>%
  filter(any(!between(wmean_fitness, -4, 4) & p_fitness_adj < 0.01)) %>% ungroup %>%
  mutate(locus = fct_cluster(locus, condition, wmean_fitness)) %>%
  mutate(wmean_fitness = wmean_fitness %>% replace(., . > 8, 8) %>% replace(., . < -8, -8))

plot_heatmap_sig <- df_heatmap %>%
  ggplot(aes(x = locus, y = condition, fill = wmean_fitness)) +
  geom_tile() + custom_theme(legend.pos = "right") +
  labs(x = paste0("genes (", length(unique(df_heatmap$locus)),")"), y = "") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank()) +
  scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
    limits = c(-8, 8))

# prepare dist object for clustering and plot dend
dist_heatmap <- df_heatmap %>% select(locus, condition, wmean_fitness) %>%
  pivot_wider(names_from = condition, values_from = wmean_fitness) %>%
  column_to_rownames(var = "locus") %>% as.matrix %>%
  dist

plot_cluster_dend <- dist_heatmap %>%
  hclust(method = "ward.D2") %>% as.dendrogram %>%
  set("branches_k_col", custom_colors[1:5], k = 5) %>%
  set("branches_lwd", 0.5) %>%
  as.ggdend %>%
  ggplot(labels = FALSE)

# arrange both on same plot
ggarrange(nrow = 2, heights =  c(0.5, 0.5),
  plot_cluster_dend + theme(plot.margin = unit(c(0.1, 0.09, -0.15, 0.136),"npc")),
  plot_heatmap_sig
)

6.2 Gene similarity by dimensionality reduction methods

We use two different dimensionality reduction methods, nMDS and t-SNE. We can check if these methods reproduce the clustering for the significantly regulated genes produced with hclust. Analysis shows that the small clusters are more strongly separated from the rest.

# set a seed to obtain same pattern for stochastic methods
set.seed(321)

# run nMDS analysis
NMDS <-  dist_heatmap %>% metaMDS
Run 0 stress 0.07875351 
Run 1 stress 0.07875392 
... Procrustes: rmse 0.0002544762  max resid 0.003174421 
... Similar to previous best
Run 2 stress 0.07875425 
... Procrustes: rmse 0.000174541  max resid 0.002128701 
... Similar to previous best
Run 3 stress 0.07875402 
... Procrustes: rmse 0.0002682146  max resid 0.003431943 
... Similar to previous best
Run 4 stress 0.07875367 
... Procrustes: rmse 0.0001044051  max resid 0.001139777 
... Similar to previous best
Run 5 stress 0.07875372 
... Procrustes: rmse 9.192768e-05  max resid 0.0009968913 
... Similar to previous best
Run 6 stress 0.0787535 
... New best solution
... Procrustes: rmse 6.47459e-05  max resid 0.00076579 
... Similar to previous best
Run 7 stress 0.07875347 
... New best solution
... Procrustes: rmse 0.000149932  max resid 0.001775701 
... Similar to previous best
Run 8 stress 0.07875417 
... Procrustes: rmse 0.0001561166  max resid 0.001996966 
... Similar to previous best
Run 9 stress 0.07875407 
... Procrustes: rmse 0.0002721831  max resid 0.003442208 
... Similar to previous best
Run 10 stress 0.07875366 
... Procrustes: rmse 0.0001259477  max resid 0.001368973 
... Similar to previous best
Run 11 stress 0.07875437 
... Procrustes: rmse 0.0001884915  max resid 0.00241518 
... Similar to previous best
Run 12 stress 0.07875413 
... Procrustes: rmse 0.0002791112  max resid 0.003533379 
... Similar to previous best
Run 13 stress 0.07875415 
... Procrustes: rmse 0.0002844956  max resid 0.003600152 
... Similar to previous best
Run 14 stress 0.07875426 
... Procrustes: rmse 0.0003004524  max resid 0.003790509 
... Similar to previous best
Run 15 stress 0.07875361 
... Procrustes: rmse 5.996707e-05  max resid 0.000589186 
... Similar to previous best
Run 16 stress 0.07875442 
... Procrustes: rmse 0.0001811358  max resid 0.002313352 
... Similar to previous best
Run 17 stress 0.07875374 
... Procrustes: rmse 0.0002147258  max resid 0.002686534 
... Similar to previous best
Run 18 stress 0.07875374 
... Procrustes: rmse 6.828352e-05  max resid 0.0008473415 
... Similar to previous best
Run 19 stress 0.07875347 
... Procrustes: rmse 2.309909e-05  max resid 0.0001711964 
... Similar to previous best
Run 20 stress 0.07875422 
... Procrustes: rmse 0.0002872007  max resid 0.003633335 
... Similar to previous best
*** Solution reached
df_nmds <- NMDS$points %>% as_tibble(rownames = "locus") %>%
  left_join(enframe(name = "locus", value = "cluster",
    cutreeord(hclust(dist_heatmap, method = "ward.D2"), k = 5)))
Joining, by = "locus"
# run t-SNE analysis
SNE <- dist_heatmap %>% tsne(max_iter = 500, perplexity = 8)
sigma summary: Min. : 0.32744177191949 |1st Qu. : 0.497094375340161 |Median : 0.550205664540542 |Mean : 0.601396916256558 |3rd Qu. : 0.65740540124671 |Max. : 1.51870623960614 |
Epoch: Iteration #100 error is: 15.8703802715933
Epoch: Iteration #200 error is: 0.563785293850083
Epoch: Iteration #300 error is: 0.543427991783255
Epoch: Iteration #400 error is: 0.535275544983855
Epoch: Iteration #500 error is: 0.531090157899214
df_tsne <- SNE %>% setNames(c("x", "y")) %>% as_tibble %>%
  mutate(locus = unique(df_heatmap$locus)) %>%
  left_join(enframe(name = "locus", value = "cluster",
    cutreeord(hclust(dist_heatmap, method = "ward.D2"), k = 5)))
Joining, by = "locus"
plot_nmds <- df_nmds %>%
  ggplot(aes(x = MDS1, y = MDS2, color = factor(cluster))) +
  geom_point(size = 2) + labs(title = "nMDS") +
  custom_theme(legend.position = c(0.85, 0.78)) +
  scale_color_manual(values = custom_colors)

plot_tsne <- df_tsne %>%
  ggplot(aes(x = V1, y = V2, color = factor(cluster))) +
  geom_point(size = 2) + labs(title = "t-SNE") +
  custom_theme(legend.position = c(0.85, 0.78)) +
  scale_color_manual(values = custom_colors)

ggarrange(ncol = 2, plot_nmds, plot_tsne)

6.3 Fit multiple linear regression models

We can find clusters of genes with similar fitness, but it is also important to identify why they cluster together. In order to find out which variables determine the fitness outcome of a gene, we can perform multiple linear regression. Each gene needs to have fitness outcomes annotated with the different (mixed) variables carbon, light, treatment. The latter can be subdivided in individual treatment columns glucose, DCMU, fluctuating light, and so on. Multiple linear regression fits a linear model of the following form to the data:

response ~ intercept + predictor A x slope A + predictor B x slope B x ...

Here, fitness is the response variable, the different conditions are the predictors. It is important to convert the categorical predictors into (numerical) dummy variables. Then for each individual gene, multiple linear models are fitted and the power of each predictor variable to predict the response is extracted.

# fixed model with 6 predictor variables -- dynamic layout would 
# be better in future
fit_linreg <- function(y, x1, x2, x3, x4, x5, x6){
  fit <- lm(y ~ x1 + x2 + x3 + x4 + x5 + x6)
  c(coefficients(fit), summary(fit)$coefficients[, 4],
    summary(fit)$r.squared)
}

# recode categorical to numerical (dummy) variables
df_linreg <- df_gene %>%
  filter(!is.na(locus)) %>%
  select(locus, carbon, light, treatment, wmean_fitness) %>% distinct %>%
  mutate(
    carbon = recode(carbon, `HC` = 1, `LC` = 0),
    light = recode(light, `LL` = 0, `IL` = 0.5, `HL` = 1)) %>%
  mutate(dummy = 1, treatment = replace(treatment, treatment == "", "-")) %>%
  pivot_wider(names_from = treatment, values_from = dummy, values_fill = 0) %>%
  mutate(`+G` = `+G` + `+D, +G`) %>% rename(`+D` = `+D, +G`) %>% select(-`-`) %>%
  # fit model
  group_by(locus) %>%
  summarize(coefficient = fit_linreg(wmean_fitness, carbon, light, `-N`, `+FL`, `+G`, `+D`),
    .groups = "keep") %>% #unnest(coefficient) %>%
  mutate(treatment = c(rep(c("intercept", "carbon", "light", "-N", "+FL", "+G", "+D"), 2) %>% 
    paste0(rep(c("", "pval_"), each = 7), .), "r_squared"))

Now we can overlay the information of the best predictor variable on the cluster map produced by tSNE, for example, and this way identify groups of genes regulated in a similar degree, by similar variables.

plot_tsne_linreg <- df_tsne %>%
  inner_join(df_linreg, by = "locus") %>%
  left_join(select(df_gene, locus, sgRNA_target) %>% distinct, by = "locus") %>%
  filter(!str_detect(treatment, "intercept|pval|r_squared")) %>%
  mutate(sgRNA_target = if_else(abs(coefficient) > 2, sgRNA_target, "")) %>%
  mutate(point_size = abs(coefficient),
    coefficient = coefficient %>% replace(., . > 5, 5) %>% replace(., . < -5, -5)) %>%
  
  ggplot(aes(x = V1, y = V2, size = point_size,
    color = coefficient, label = sgRNA_target)) +
  geom_point() +
  labs(title = "t-SNE clustering of genes with absolute fitness > 4 and p-value < 0.01.", 
    subtitle = paste0("dot color/size encodes effect of variable, n = ", nrow(df_tsne))) +
  custom_theme(aspect = 1) +
  scale_color_gradientn(limits = c(-5, 5),
    colours = c(custom_colors[1], grey(0.6, 0.8), custom_colors[2])) +
  scale_size_continuous(range = c(1, 6)) +
  geom_text_repel(size = 3, max.overlaps = 50) +
  facet_wrap( ~ treatment, ncol = 2)

print(plot_tsne_linreg)

This strategy reveals a list of interesting condition-specific genes:

  • Fluctuating light:
    • sll0217 - Putative diflavin flavoprotein A2 (dfa2 / Flv4)
    • sll0218 - Putative diflavin flavoprotein associated protein
    • Putative diflavin flavoproteins sll1521 (Flv1) and sll0550 (Flv3) show inverse but FL+ specific fitness pattern but were not included in figure because of lower significance/FC
  • Mixotrophy:
    • sll0593 - glk, glucokinase, catalyzes P-ylation of Glc to G6P
    • sll1533 - pilT, fimbria assembly, mobility, Glc transport or sensing?
    • ssl3364 - CP12 small protein, strongly interacts with RbcX, RbcR, Prk. Novel C-metabolism regulator
  • Light:
    • ssr2142 ycf19, short unknown protein, interacts with psbO and Tat membrane protein insertion system,
    • slr0963 sir, sulfite reductase, ferredoxin H2O + HS + ferredoxin <-> H+ + reduced ferredoxin + sulfite, strongly interacts with other proteins in sulfur metabolism, specifically related to cofactor biosynthesis, cobalamin (vitamin B12) and siroheme
  • Light, mixotrophy, heterotrophy: cluster of photosynthesis related genes increase fitness when KOed: apcA,D,E, psbB,C,D
  • Carbon:
    • sll0217 Putative diflavin flavoprotein A2 (dfa2), KO negatively correlated with fitness with C, positive with +FL
    • sll0218 same behavior as dfa2, interacts with dfa2,4, contributes to PSII stabilization, Bersanini et al., 2017. Negative fitness score probably side effect of downstream KD of sll0219 (Flv2). That shows essentially same pattern as sll0217 and sll0218 but weaker effect on fitness.

6.4 List of genes with strong fitness correlation

The table with linear regression coefficients and p-values is reshaped to long format for better readability. The kableExtra package is used to color cells for easier recognition. Then we subset the table for each treatment in order to spot the most interesting genes.

df_linreg_wide <- df_linreg %>%
  pivot_wider(names_from = treatment, values_from = coefficient) %>%
  left_join(select(df_gene, locus, sgRNA_target) %>% distinct, by = "locus") %>%
  select(-matches("intercept")) %>%
  filter(if_any(matches("^(carb|light|\\-|\\+)"), ~ abs(.) > 2)) %>%
  mutate(across(matches("carb|light|\\-|\\+"), ~ round(., 3))) %>% 
  ungroup %>% select(sgRNA_target, locus, matches("."))

color_table <- function(df, variable) {
  filter(df, abs(.data[[variable]]) > 2) %>% 
  select(matches("^(sg|loc|r_s|carb|light|\\-|\\+)") | all_of(paste0("pval_", variable))) %>%
  arrange(desc(.data[[variable]])) %>%
  mutate(across(3:8, ~ cell_spec(., "html", color = "white",
      background = spec_color(., option = "E", scale = c(-5.5, 5.5)),
      bold = TRUE))) %>%
  kbl(format = "html", escape = F) %>%
  kable_paper("striped", full_width = F)
}
df_linreg_wide %>% color_table("carbon")
sgRNA_target locus carbon light -N +FL +G +D r_squared pval_carbon
slr1095 slr1095 2.387 -2.142 -1.309 -1.578 -0.291 1.755 0.6393896 0.083
sll1734 sll1734 2.38 -1.215 -0.491 -1.57 0.815 1.159 0.6618253 0.094
ftsZ sll1633 2.358 -2.338 -1.65 -1.336 0.382 2.217 0.9249818 0.006
ndhD3 sll1733 2.306 -1.205 -0.488 -1.413 0.697 1.142 0.6648586 0.086
slr0211 slr0211 2.275 0.485 -0.042 -0.3 0.76 1.827 0.8191084 0.023
ndhF2 sll1732 2.174 -0.927 -0.642 -1.41 0.981 1.13 0.6679809 0.098
slr1818 slr1818 2.153 -1.092 -1.322 -1.445 -0.017 1.658 0.5834552 0.113
sll0481 sll0481 2.138 -3.099 -1.468 -1.23 0.573 1.028 0.9605706 0.002
ssr3532 ssr3532 2.123 -1.513 -3.614 -1.364 0.629 1.563 0.6053045 0.170
sll0995 sll0995 2.091 -0.346 -1.947 -1.25 0.68 1.368 0.5977401 0.132
sll0488 sll0488 2.09 -1.239 -1.395 -1.411 0.002 1.57 0.6282919 0.091
rpl11 sll1743 -2.013 1.249 1.367 1.394 -0.435 -1.618 0.7577864 0.051
rpl24 sll1807 -2.045 1.011 0.628 1.897 0.058 -1.195 0.4892608 0.205
slr6107 slr6107 -2.065 1.097 0.977 1.058 -0.349 -1.135 0.5534798 0.122
rps9 sll1822 -2.068 0.912 0.715 1.345 -0.573 -1.086 0.5757896 0.133
rps17 ssl3437 -2.145 0.921 0.964 1.905 -0.086 -1.249 0.4865994 0.202
leuD sll1444 -2.175 2.482 -0.196 1.635 -1.481 -0.021 0.6729225 0.150
slr0007 slr0007 -2.278 1.818 0.698 1.953 -0.816 -1.094 0.6695320 0.112
rpl35 ssl1426 -2.28 1.706 1.534 1.175 -0.442 -1.582 0.5831989 0.118
rps15 ssl1784 -2.439 1.617 1.137 2.496 -0.689 -1.262 0.7329430 0.081
sll0218 sll0218 -2.451 1.816 0.706 1.69 -0.261 -1.129 0.5975000 0.115
sll0217 sll0217 -2.601 1.899 1.01 2.16 -0.332 -1.27 0.6091951 0.121
df_linreg_wide %>% color_table("light")
sgRNA_target locus carbon light -N +FL +G +D r_squared pval_light
apcE slr0335 -0.364 4.396 0.614 1.171 3.377 1.75 0.7986422 0.065
sll1878 sll1878 -1.575 4.254 2.026 2.134 1.289 -0.239 0.7970770 0.024
apcA slr2067 0.198 3.983 0.247 0.519 2.911 2.944 0.7278894 0.149
cpcB sll1577 0.162 3.945 0.167 0.701 2.497 2.452 0.8244936 0.055
hitB slr0327 -1.668 3.449 1.498 1.602 0.551 -0.453 0.6647072 0.089
cpcA sll1578 0.109 3.447 0.248 0.575 2.187 2.16 0.8373334 0.049
sll1378 sll1378 -0.666 3.254 -0.297 1.257 2.448 0.995 0.9494232 0.006
sir slr0963 -1.512 3.249 0.87 1.129 -0.223 -0.092 0.7831010 0.049
slr1102 slr1102 0.063 3.215 -0.122 0.516 2.794 0.854 0.8202867 0.067
slr1990 slr1990 -0.796 3.197 0.308 0.983 2.461 2.377 0.8885079 0.046
slr0947 slr0947 0.237 3.195 0.326 0.7 1.452 0.583 0.7764230 0.035
sll6055 sll6055 -1.134 3.183 0.652 0.596 2.428 2.03 0.8430558 0.079
sll1879 sll1879 -0.19 3.167 -0.9 -0.451 0.571 -0.553 0.7003077 0.101
amiC slr0447 -1.211 3.079 0.487 -0.249 1.266 0.371 0.9335177 0.007
sll0689 sll0689 -0.153 3.025 0.167 0.426 -0.02 0.204 0.5047466 0.176
sll1945 sll1945 -0.183 2.885 0.656 0.631 0.023 1.261 0.7071607 0.060
narB sll1454 -0.167 2.862 0.022 -0.002 0.015 0.611 0.8345436 0.024
slr1505 slr1505 -0.671 2.821 0.479 0.822 2.18 3.08 0.9240662 0.037
nirA slr0898 -1.08 2.813 -0.125 0.467 -0.386 0.012 0.7484847 0.073
def slr1549 0.819 2.803 1.024 -0.093 1.666 0.926 0.7577032 0.091
apcB slr1986 0.601 2.717 0.37 0.452 2.055 2.18 0.7645135 0.125
slr0483 slr0483 0.195 2.713 0.552 0.615 0.922 0.411 0.9326832 0.003
ycf38 sll0760 -0.429 2.681 -0.319 0.194 0.777 1.482 0.6765647 0.122
slr0734 slr0734 0.53 2.675 0.525 0.69 1.903 2.168 0.8201447 0.072
ssl0331 ssl0331 -1.177 2.603 1.116 1.281 0.45 -1.158 0.8581214 0.020
slr1302 slr1302 -1.019 2.583 0.096 0.473 1.356 -0.205 0.8574274 0.024
psbO sll0427 0.648 2.564 0.261 0.641 3.195 1.61 0.8810954 0.082
slr1170 slr1170 -0.705 2.558 -0.682 0.279 0.545 -0.534 0.7209869 0.079
hemB sll1994 -0.065 2.552 -0.731 0.674 0.631 0.76 0.6651582 0.102
cpcG slr2051 -0.178 2.549 0.214 0.476 2.241 1.479 0.9153217 0.026
slr2042 slr2042 -0.391 2.547 0.808 1.126 1.245 -1.183 0.8863271 0.007
slr7096 slr7096 -0.444 2.528 -0.447 0.569 0.014 0.165 0.9152988 0.007
slr1693 slr1693 -1.57 2.514 0.577 0.919 -0.368 -1.034 0.8421593 0.050
cyp2 slr0574 -0.403 2.498 0.815 0.757 1.725 -0.169 0.9262507 0.004
cysH slr1791 -1.957 2.488 0.633 1.256 0.11 -1.031 0.8451493 0.056
leuD sll1444 -2.175 2.482 -0.196 1.635 -1.481 -0.021 0.6729225 0.316
sll0148 sll0148 0.326 2.475 -0.282 -0.136 1.902 0.084 0.7705840 0.092
psbJ smr0008 0.314 2.456 0.122 0.338 2.783 3.173 0.8857967 0.121
nrtB sll1451 -0.098 2.451 0.238 0.366 -0.286 1.265 0.8920322 0.012
moeB sll1536 -0.846 2.444 1.053 0.242 -0.079 -0.066 0.7547841 0.048
plsX slr1510 0.943 2.435 0.207 0.039 0.172 1.835 0.6187582 0.191
sll1500 sll1500 -1.295 2.396 0.049 0.294 -0.833 -0.611 0.7136572 0.145
drgA slr1719 -1.597 2.388 0.612 1.508 -0.229 -0.38 0.6708782 0.175
trxA3 slr0623 -1.337 2.38 1.128 1.816 0.157 -0.616 0.7975914 0.066
petH slr1643 -1.159 2.364 0.986 1.517 1.958 -2.802 0.7467353 0.102
sll6109 sll6109 -0.843 2.36 0.496 0.647 0.071 0.133 0.8747899 0.012
sll0301 sll0301 -0.03 2.357 -0.251 0.012 2.284 0.541 0.9050425 0.034
slr1692 slr1692 0.449 2.342 0.407 0.678 1.441 1.464 0.8349437 0.041
murC slr1423 0.088 2.259 0.646 0.533 0.439 0.156 0.7159902 0.046
hemC slr1887 -0.713 2.255 0.153 1.018 0.162 0.393 0.6518397 0.098
proC slr0661 -0.702 2.228 0.131 -0.058 1.458 1.618 0.8773245 0.059
fabF sll1069 0.29 2.191 1.68 0.162 0.353 -0.104 0.8063859 0.047
sll1380 sll1380 -0.341 2.177 0.475 0.687 1.593 0.61 0.9381576 0.006
slr1042 slr1042 0.589 2.177 0.558 0.466 0.711 0.828 0.5188764 0.188
trpF sll0356 -0.026 2.171 0.066 0.45 -0.776 1.19 0.7624836 0.079
psbD sll0849 -0.385 2.168 0.689 0.086 2.733 3.26 0.8715140 0.198
sll1304 sll1304 -0.556 2.164 0.59 0.506 0.596 0.6 0.8607016 0.013
rpiA slr0194 -0.285 2.164 1.665 1.548 2.492 -1.479 0.9595789 0.003
sll0847 sll0847 -0.928 2.146 -0.004 0.095 0.645 0.366 0.7384118 0.083
slr0950 slr0950 -0.06 2.12 0.471 0.285 1.434 0.103 0.8624219 0.019
glnA slr1756 -0.712 2.116 0.069 0.289 -1.319 -0.039 0.8326578 0.083
prk sll1525 0.406 2.108 0.737 0.758 0.508 -1.476 0.6328077 0.181
slr0771 slr0771 -0.118 2.107 0.442 0.427 0.244 -0.059 0.8105120 0.021
ssl1918 ssl1918 -0.051 2.089 0.294 0.452 1.252 0.491 0.7714122 0.047
thrA sll0455 0.511 2.085 -0.369 -0.448 0.795 0.383 0.6493295 0.168
slr1841 slr1841 -0.099 2.06 0.972 0.467 0.12 0.278 0.7563574 0.038
sll2003 sll2003 -0.309 2.01 0.456 0.168 1.414 0.481 0.8812229 0.022
aroB slr2130 -1.172 2.006 -0.708 0.796 -0.482 -0.073 0.8229669 0.085
slr0909 slr0909 1.698 -2.006 1.684 -0.624 -1.31 0.509 0.7708259 0.243
sll0176 sll0176 -0.446 -2.041 -2.177 -1.757 -1.588 1.036 0.6316911 0.190
clpP4 sll0534 -0.189 -2.088 -1.279 -0.661 -0.918 -0.248 0.7374180 0.043
slr1095 slr1095 2.387 -2.142 -1.309 -1.578 -0.291 1.755 0.6393896 0.308
sll0162 sll0162 0.205 -2.169 -0.399 -0.247 -0.38 0.04 0.6288004 0.080
mraY sll0657 1.191 -2.225 -0.742 -0.967 -0.87 0.798 0.8828557 0.013
ssr0657 ssr0657 0.851 -2.255 -0.628 -0.922 -1.928 1.38 0.9168860 0.009
slr0484 slr0484 0.155 -2.275 -0.737 -0.056 -0.403 0.018 0.7284099 0.048
sll1757 sll1757 0.782 -2.308 -0.514 -0.725 -1.523 -0.126 0.5443160 0.188
sll1915 sll1915 0.295 -2.33 -0.146 -0.393 -1.358 -0.211 0.6252561 0.117
ftsZ sll1633 2.358 -2.338 -1.65 -1.336 0.382 2.217 0.9249818 0.039
mrdB slr1267 0.165 -2.34 -0.19 -0.178 -1.214 0.571 0.8496898 0.017
gcvP slr0293 1.813 -2.377 -1.53 -0.666 -0.577 1.667 0.9508828 0.005
psaK ssr0390 1.174 -2.464 -1.418 -0.794 -0.759 0.749 0.6957949 0.063
slr0643 slr0643 0.13 -2.492 -0.444 0.121 -0.617 0.274 0.5479555 0.144
ppiB sll0227 0.726 -2.506 -1.003 -0.116 -0.713 0.3 0.8570898 0.013
dnaJ4 sll0897 0.947 -2.514 -0.084 0.166 -0.701 1.75 0.9249765 0.008
sll0498 sll0498 0.382 -2.57 -1.098 -1.094 -2.128 -1.987 0.8450652 0.066
rpoE slr1545 0.948 -2.811 -0.454 0.775 -1.213 1.284 0.5539985 0.213
clpX sll0535 0.719 -2.92 -1.034 -1.101 -1.482 -0.018 0.7466515 0.035
sll0481 sll0481 2.138 -3.099 -1.468 -1.23 0.573 1.028 0.9605706 0.004
sll0877 sll0877 1.738 -3.53 -1.875 -1.161 -1.126 0.944 0.7859411 0.033
ycf19 ssr2142 0.939 -3.598 -0.212 0.316 -2.505 1.489 0.5860186 0.188
df_linreg_wide %>% color_table("-N")
sgRNA_target locus carbon light -N +FL +G +D r_squared pval_-N
atpA sll1326 -0.954 0.056 2.134 0.113 0.353 -1.925 0.3528101 0.368
sll1878 sll1878 -1.575 4.254 2.026 2.134 1.289 -0.239 0.7970770 0.176
slr1079 slr1079 1.73 -1.557 -2.055 -1.162 -0.44 1.891 0.5608596 0.290
sll0176 sll0176 -0.446 -2.041 -2.177 -1.757 -1.588 1.036 0.6316911 0.175
ssr3532 ssr3532 2.123 -1.513 -3.614 -1.364 0.629 1.563 0.6053045 0.192
df_linreg_wide %>% color_table("+FL")
sgRNA_target locus carbon light -N +FL +G +D r_squared pval_+FL
rps15 ssl1784 -2.439 1.617 1.137 2.496 -0.689 -1.262 0.7329430 0.163
pyrG sll1443 -1.831 0.64 0.042 2.297 -1.164 -0.365 0.6783065 0.208
sll0217 sll0217 -2.601 1.899 1.01 2.16 -0.332 -1.27 0.6091951 0.305
sll1878 sll1878 -1.575 4.254 2.026 2.134 1.289 -0.239 0.7970770 0.087
ribA sll1894 -0.855 -0.295 -0.15 -2.047 -0.667 -0.661 0.5923193 0.120
ribC sll0300 -1.026 -0.401 -0.288 -2.078 -0.695 -0.7 0.8137360 0.030
sll1521 sll1521 -0.281 -0.694 -0.495 -3.573 -0.617 -0.353 0.9592191 0.001
df_linreg_wide %>% color_table("+G")
sgRNA_target locus carbon light -N +FL +G +D r_squared pval_+G
dgt sll0398 -0.441 0.489 0.245 1.003 3.626 -0.206 0.9822154 0.000
sll1496 sll1496 1.391 -1.2 -0.421 -0.663 3.469 -2.512 0.9242226 0.009
apcE slr0335 -0.364 4.396 0.614 1.171 3.377 1.75 0.7986422 0.068
psbO sll0427 0.648 2.564 0.261 0.641 3.195 1.61 0.8810954 0.021
psbC sll0851 0.075 1.323 0.132 0.239 2.948 3.841 0.9344577 0.024
apcA slr2067 0.198 3.983 0.247 0.519 2.911 2.944 0.7278894 0.172
slr0758 slr0758 -0.683 1.957 0.28 -0.181 2.866 -1.546 0.9470649 0.003
slr1102 slr1102 0.063 3.215 -0.122 0.516 2.794 0.854 0.8202867 0.050
psbJ smr0008 0.314 2.456 0.122 0.338 2.783 3.173 0.8857967 0.047
psbD sll0849 -0.385 2.168 0.689 0.086 2.733 3.26 0.8715140 0.068
ssr2062 ssr2062 0.494 0.079 0.136 -0.099 2.664 -1.726 0.9362954 0.003
sll0556 sll0556 1.231 0.919 0.251 -0.367 2.65 0.098 0.7882643 0.058
slr2070 slr2070 0.803 -0.583 -0.419 -0.647 2.635 -1.692 0.7395128 0.078
cpcB sll1577 0.162 3.945 0.167 0.701 2.497 2.452 0.8244936 0.096
rpiA slr0194 -0.285 2.164 1.665 1.548 2.492 -1.479 0.9595789 0.001
cbbA sll0018 -0.676 1.173 0.943 0.582 2.49 1.976 0.9658786 0.004
slr1990 slr1990 -0.796 3.197 0.308 0.983 2.461 2.377 0.8885079 0.048
sll1378 sll1378 -0.666 3.254 -0.297 1.257 2.448 0.995 0.9494232 0.007
sll6055 sll6055 -1.134 3.183 0.652 0.596 2.428 2.03 0.8430558 0.085
psbH ssl2598 0.276 1.685 0.245 -0.08 2.331 1.252 0.8998008 0.020
cpcC2 sll1579 -0.025 1.082 0.124 0.199 2.301 0.617 0.9789188 0.001
sll0301 sll0301 -0.03 2.357 -0.251 0.012 2.284 0.541 0.9050425 0.018
trx sll1057 -0.433 1.05 -0.053 0.094 2.271 0.885 0.9646268 0.003
cpcG slr2051 -0.178 2.549 0.214 0.476 2.241 1.479 0.9153217 0.018
sll0062 sll0062 0.576 1.109 0.199 0.615 2.196 0.933 0.8425951 0.029
cpcA sll1578 0.109 3.447 0.248 0.575 2.187 2.16 0.8373334 0.086
slr1505 slr1505 -0.671 2.821 0.479 0.822 2.18 3.08 0.9240662 0.038
entC slr0817 -0.296 -0.001 -0.214 -1.438 2.154 0.697 0.9237197 0.028
ccmK4 slr1839 -0.225 1.996 0.537 0.391 2.124 0.313 0.7907088 0.045
ndhF slr2009 0.248 1.987 0.747 0.498 2.074 3.01 0.9320576 0.024
apcB slr1986 0.601 2.717 0.37 0.452 2.055 2.18 0.7645135 0.135
slr0756 slr0756 0.142 0.859 0.487 -0.181 2.038 -1.588 0.8503634 0.016
ccsA sll1513 0.332 1.291 -0.132 0.211 2.016 0.773 0.8613925 0.028
glcP sll0771 0.124 -0.402 -0.152 -0.005 -2.022 -0.925 0.9967108 0.000
slr1974 slr1974 -1.282 -0.249 0.537 0.638 -2.026 -0.747 0.7336076 0.139
purA sll1823 -1.268 -0.625 1.036 0.424 -2.098 -0.559 0.6756686 0.170
sll0498 sll0498 0.382 -2.57 -1.098 -1.094 -2.128 -1.987 0.8450652 0.056
minE ssl0546 0.965 -1.75 -1.011 -0.822 -2.131 0.14 0.8209195 0.034
pilT sll1533 -1.061 -0.01 -0.47 1.482 -2.183 0.905 0.6864409 0.178
ycf19 ssr2142 0.939 -3.598 -0.212 0.316 -2.505 1.489 0.5860186 0.232
glgA sll1393 -0.118 -0.256 -0.035 -0.211 -2.759 1.551 0.9313846 0.003
ssl3364 ssl3364 0.202 -1.325 -0.621 -0.262 -2.962 -0.627 0.9069737 0.011
glk sll0593 0.026 -0.765 -0.624 -0.439 -3.7 -2.082 0.9847732 0.001
df_linreg_wide %>% color_table("+D")
sgRNA_target locus carbon light -N +FL +G +D r_squared pval_+D
psbB slr0906 -0.01 1.654 0.62 -0.247 1.429 4.713 0.8746016 0.026
psbE ssr3451 0.201 1.57 0.042 0.132 1.445 3.998 0.9267375 0.010
psbC sll0851 0.075 1.323 0.132 0.239 2.948 3.841 0.9344577 0.025
psbF smr0006 0.246 1.155 -0.03 -0.064 0.89 3.798 0.9362166 0.006
rpoDI slr0653 1.795 -0.655 -1.058 -1.351 -1.3 3.369 0.5929767 0.153
psbD2 slr0927 -0.006 1.223 0.208 -0.251 1.904 3.33 0.9117241 0.029
psbD sll0849 -0.385 2.168 0.689 0.086 2.733 3.26 0.8715140 0.088
psbJ smr0008 0.314 2.456 0.122 0.338 2.783 3.173 0.8857967 0.070
slr1505 slr1505 -0.671 2.821 0.479 0.822 2.18 3.08 0.9240662 0.031
ndhF slr2009 0.248 1.987 0.747 0.498 2.074 3.01 0.9320576 0.018
apcA slr2067 0.198 3.983 0.247 0.519 2.911 2.944 0.7278894 0.271
tktA sll1070 0.255 0.63 0.494 0.794 1.829 2.84 0.9506669 0.009
lysA sll0504 -0.019 0.382 0.298 0.073 0.181 2.777 0.9584028 0.002
cpcB sll1577 0.162 3.945 0.167 0.701 2.497 2.452 0.8244936 0.182
slr1990 slr1990 -0.796 3.197 0.308 0.983 2.461 2.377 0.8885079 0.108
psbL smr0007 0.249 1.203 0.047 0.33 1.149 2.334 0.7682196 0.095
ftsZ sll1633 2.358 -2.338 -1.65 -1.336 0.382 2.217 0.9249818 0.050
apcB slr1986 0.601 2.717 0.37 0.452 2.055 2.18 0.7645135 0.207
slr0734 slr0734 0.53 2.675 0.525 0.69 1.903 2.168 0.8201447 0.129
cpcA sll1578 0.109 3.447 0.248 0.575 2.187 2.16 0.8373334 0.164
hemA slr1808 0.761 1.112 -0.618 -0.409 1.13 2.043 0.5582624 0.334
sll6055 sll6055 -1.134 3.183 0.652 0.596 2.428 2.03 0.8430558 0.222
rub slr2033 0.207 1.32 0.165 -0.525 1.987 2.007 0.9926877 0.001
ssr2333 ssr2333 0.266 -1.348 0.007 -0.036 0.267 -2.077 0.4289894 0.278
glk sll0593 0.026 -0.765 -0.624 -0.439 -3.7 -2.082 0.9847732 0.013
atpI sll1322 -1.3 -0.22 1.386 0.618 0.314 -2.115 0.3752932 0.385
ndhC slr1279 0.91 -0.168 0.404 -0.205 -0.952 -2.132 0.8559521 0.101
pmgA sll1968 1.366 -1.91 0.217 -1.32 -1.804 -2.189 0.5865345 0.450
ssl0438 ssl0438 0.128 -1.368 -0.221 -0.362 -0.19 -2.306 0.8694021 0.018
slr1245 slr1245 -1.857 1.097 0.644 -0.267 0.778 -2.375 0.8171802 0.060
sll1496 sll1496 1.391 -1.2 -0.421 -0.663 3.469 -2.512 0.9242226 0.060
slr1098 slr1098 0.853 -1.993 -0.12 -0.348 0.546 -2.606 0.4031596 0.356
petH slr1643 -1.159 2.364 0.986 1.517 1.958 -2.802 0.7467353 0.072
talB slr1793 0.164 -0.113 -0.78 -0.02 0.134 -2.817 0.9822722 0.000
zwf slr1843 -0.092 0.354 -0.098 0.085 0.025 -2.896 0.9971175 0.000

Based on the multiple linear model correlations, we can try to extract a shortlist of the most interesting hypothetical genes. These could warrant further investigations.

list_top_unknown_hits <- df_linreg_wide %>%
  left_join(df_uniprot, by = "locus") %>%
  # filter by name: only unknown proteins
  filter(
    is.na(gene_name_short),
    str_detect(protein, "[a-zA-Z]{3}[0-9]{4} protein|Uncharacterized")) %>%
  # filter by effect: only correlation > 3
  filter(if_any(matches("^(carb|light|\\-|\\+)"), ~ abs(.) > 3)) %>%
  arrange(desc(r_squared)) %>%
  pull(locus)

df_linreg_wide %>% filter(locus %in% list_top_unknown_hits) %>%
  select(!starts_with("pval"), -sgRNA_target) %>%
  mutate(across(2:7, ~ cell_spec(., "html", color = "white",
      background = spec_color(., option = "E", scale = c(-5.5, 5.5)),
      bold = TRUE))) %>%
  kbl(format = "html", escape = F) %>%
  kable_paper("striped", full_width = F)
locus carbon light -N +FL +G +D r_squared
sll0481 2.138 -3.099 -1.468 -1.23 0.573 1.028 0.9605706
sll0877 1.738 -3.53 -1.875 -1.161 -1.126 0.944 0.7859411
sll1378 -0.666 3.254 -0.297 1.257 2.448 0.995 0.9494232
sll6055 -1.134 3.183 0.652 0.596 2.428 2.03 0.8430558
slr1102 0.063 3.215 -0.122 0.516 2.794 0.854 0.8202867
slr1505 -0.671 2.821 0.479 0.822 2.18 3.08 0.9240662
slr1990 -0.796 3.197 0.308 0.983 2.461 2.377 0.8885079
ssr3532 2.123 -1.513 -3.614 -1.364 0.629 1.563 0.6053045

6.5 Extract and analyze interesting gene clusters

The list above shows the genes whose fitness is most significantly correlated with one of the treatments. This list of genes (length: 8) is extracted and then simply fitness per condition is plotted as a heat map, in order to confirm the trends from fitting the multiple liner regression models. Note that this list of genes was not filtered by fitness p-value, but only derived from multiple linear regression. The fitness p-value is indicated in the figure if p <= 0.1.

plot_unknown_genes <- df_gene %>%
  filter(locus %in% list_top_unknown_hits, time == 0) %>%
  mutate(sgRNA_target = fct_cluster(sgRNA_target, condition, wmean_fitness)) %>%
  mutate(condition = fct_cluster(condition, sgRNA_target, wmean_fitness)) %>%
  mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
  mutate(p_label = if_else(p_fitness_adj <= 0.1, as.character(round(p_fitness_adj, 2)), "")) %>%
  ggplot(aes(x = condition, y = sgRNA_target, fill = wmean_fitness, label = p_label)) +
  geom_tile() + custom_theme() +
  geom_text(size = 3) +
  labs(title = "Top unknown genes, fitness.", x = "", y = "") +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
  scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
    limits = c(-4, 4))

print(plot_unknown_genes)

Summary

  • sll0877 - 456 AA. KD has higher fitness only in HC,LL. Mitigates light limitation?
  • sll0481 - 155 AA. KD has higher fitness in +G conditions and lower fitness in HL. Membrane localization. Negatively regulating glycolysis?
  • ssr3532 - 80 AA. KD lower fitness on N-limitation and C-limitation (LC-HL combinations). Same operon as glutaminase glsA (slr2079, catalyzes deamination of gln –> glu), regulatory, involved in N metabolism?
  • slr1102 - 853 AA. KD has lower fitness on all LL conditions. 4 known domains, FHA (forkhead-associated domain is a phosphopeptide recognition domain found in many regulatory proteins), PAS (signaling, often involved in circadian proteins, detect their signal by way of an associated cofactor like heme, flavin), GGDEF (involved in signal transduction, likely to catalyze synthesis or hydrolysis of cyclic diguanylate c-diGMP), EAL (shown to stimulate degradation of a second messenger, cyclic di-GMP, candidate for a diguanylate phosphodiesterase function. Together with the GGDEF domain, EAL might be involved in regulating cell surface adhesiveness in bacteria). Source: InterPro. Embedded in a tight network of interacting proteins all involved in chromophore biosynthesis/maturation.
  • sll1378 - 300 AA. KD has lower fitness on all LL conditions. Membrane associated protein? In STRING, potential interaction with PbsA1 and PbsA2 (Heme oxygenase 1 and 2). Potentially important for chlorophyll or heme biosynthesis –> would explain importance for photosynthesis in LL condition.
  • slr1505 - 198 AA. Fitness profile as above. No useful information.
  • sll6055 - 152 AA. Fitness profile as above. Multiubiquitin domain, involved in protein modification/degradation of PS proteins?
  • slr1990 - 240 AA, 5 TM domains. KD higher fitness in photoheterotrophy, lower fitness in all HC/LL conditions. Something important for photosystems? Something that wastes e- in photoheterotrophic conditions?

Apc and cpc repression mutants encoding phycobilisomes are also enriched in high light

plot_sgRNAs_phycobil <- df_gene %>%
  filter(str_detect(gene_name, "[ac]pc"), time == 0) %>%
  mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
  ggplot(aes(x = condition, y = fct_rev(sgRNA_target), fill = wmean_fitness)) +
  geom_tile() + custom_theme() +
  labs(title = "Apc/Cpc repression mutants", x = "", y = "") +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
  scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
    limits = c(-4, 4))

print(plot_sgRNAs_phycobil)

7 Direct comparison of gene fitness

7.1 Fitness of all conditions vs each other

We can plot selected conditions against each other and add gene labels in order to find or confirm particular patterns.

make_fitness_plot <- function(data, vars, title = NULL) {
  # prepare data for two  variables each
  data %>% ungroup %>%
    filter(condition %in% vars, sgRNA_type == "gene") %>%
    select(locus, sgRNA_target, condition, wmean_fitness) %>% distinct %>%
    pivot_wider(names_from = condition, values_from = wmean_fitness) %>%
    mutate(
      dfit = get(vars[1]) - get(vars[2]),
      significant = !between(dfit, quantile(dfit, probs = c(0.003)),
        quantile(dfit, probs = c(0.997))),
      sgRNA_target = if_else(significant, sgRNA_target, "")) %>%
    
    # plot
    ggplot(aes(x = get(vars[1]), y = get(vars[2]), 
      color = significant, label = sgRNA_target)) +
    geom_point(size = 1) + custom_theme(legend.position = 0) +
    geom_abline(intercept = 0, slope = 1, col = grey(0.5), lty = 2, size = 0.8) +
    geom_abline(intercept = 4, slope = 1, col = grey(0.5), lty = 2, size = 0.8) +
    geom_abline(intercept = -4, slope = 1, col = grey(0.5), lty = 2, size = 0.8) +
    geom_text_repel(size = 3, max.overlaps = 50) +
    labs(title = title, x = vars[1], y = vars[2]) +
    coord_cartesian(xlim = c(-9, 5), ylim = c(-9, 5)) +
    scale_color_manual(values = c(grey(0.5), custom_colors[2]))
}

# browse through all possible condition combinations;
# we need a helper function that detects duplicated combinations
duplicated_2vec <- function(x, y) {
  xy = paste(x, y); yx = paste(y, x)
  sapply(xy, function(xval) {
    which(xval == yx) <= which(xval == xy)
  })
}

list_condition_pairs <- lapply(
  unique(df_gene$condition) %>% expand_grid(x = ., y = .) %>%
    filter(!duplicated_2vec(x, y)) %>% t %>% as.data.frame %>% as.list,
  function(var) {
    make_fitness_plot(df_gene, vars = var,
      title = paste(var, collapse = "  -  "))
  }
)

# export images
invisible(capture.output(
  lapply(list_condition_pairs, function(pl) {
    pl_name <- paste0("../figures/pairwise_comparisons/plot_", pl$labels$x, "_", pl$labels$y, ".png")
    png(filename = pl_name, width = 800, height = 800, res = 120)
    print(pl)
    dev.off()
  })
))
# example of first 4 combinations
list_condition_pairs[1:4]
$V1

$V2

$V3

$V4

8 Differential fitness of selected gene sets

8.1 Central carbon metabolism

To plot gene fitness for the enzymes of central carbon metabolism, we use the complete list of enzymes and the genes that they are mapped to (obtained from KEGG). We can extract gene sets for specific pathways and plot fitness. We start with glycolysis and Calvin cycle enzymes.

list_central_met_pathways <- c(
  "Glycolysis / Gluconeogenesis",
  "Pentose phosphate pathway",
  "Carbon fixation in photosynthetic organisms",
  "Photosynthesis",
  "Citrate cycle (TCA cycle)",
  "Pyruvate metabolism",
  "Glyoxylate and dicarboxylate metabolism"
)
plot_gene_fitness <- function(df, pw = NULL, gene = NULL,
  title = NULL, ncol = 8, legend.position = "bottom") {
  df <- df %>% filter(time == 0)
  if (!is.null(pw)) {
    df <- df %>% inner_join(df_kegg %>% filter(kegg_pathway == pw) %>% select(locus),
      by = "locus")
    title <- pw
  } else if (!is.null(gene)) {
    df <- df %>% filter(locus %in% gene)
  }
  
  ggplot(df, aes(x = condition, y = wmean_fitness, 
    ymin = wmean_fitness-sd_fitness, 
    ymax = wmean_fitness+sd_fitness,
    fill = condition,
    color = condition,
    label = if_else(p_fitness_adj <= 0.01, "*", ""))) +
    geom_col(position = "dodge", width = 0.6) +
    geom_errorbar(position = "dodge", width = 0.6, size = 1) +
    geom_text(size = 5, aes(y = mapply(FUN = function(x, y) {
      if (x < 0) x-y-2
      else x+y+0.3
    }, wmean_fitness, sd_fitness))) +
    custom_theme(aspect.ratio = 1,
      legend.position = legend.position, legend.key.size = unit(0.4, "cm")) + 
    labs(title = title, x = "", y = "fitness") +
    theme(axis.text.x = element_blank(), axis.ticks = element_blank()) +
    scale_fill_manual(values = colorRampPalette(custom_colors[1:5])(11)) +
    scale_color_manual(values = colorRampPalette(custom_colors[1:5])(11)) +
    facet_wrap(~ sgRNA_target, ncol = ncol, drop = FALSE)
}
print(plot_gene_fitness(df_gene, pw = list_central_met_pathways[[1]]))

print(plot_gene_fitness(df_gene, pw = list_central_met_pathways[[2]]))

print(plot_gene_fitness(df_gene, pw = list_central_met_pathways[[3]]))

print(plot_gene_fitness(df_gene, pw = list_central_met_pathways[[5]]))

8.2 Gene fitness in mixotrophy and heterotrophy

Using fluctuator, we can import a custom metabolic map for Synechocystis sp. PCC 6803, and overlay published fluxes that were measured with LC-MS using isotopically labelled carbon sources (Nakajima et al., 2014).

Fluctuator can be installed using a function from devtools:

devtools::install_github("m-jahn/fluctuator")

We import the metabolic flux data from the supplemental items of Nakajima et al., 2014.

library(fluctuator)

# import flux data
df_nakajima_mfa <- read.csv("../data/input/Nakajima2014_metabolic_fluxes.csv")

# generate stroke width and color
df_nakajima_mfa <- df_nakajima_mfa %>%
  mutate(
    stroke_width = 0.3 + (0.7*sqrt(abs(flux))),
    stroke_color = abs(flux) %>% {1+(./max(.))*9} %>% round,
    stroke_color_rgb =  colorRampPalette(custom_colors[c(5,2,1)])(10)[stroke_color])

The next step is to overlay the fluxes. We generate two types of maps, mixotrophy and photoheterotrophy. The stroke width and color for all reactions is set by the flux magnitude.

for (cond in c("mixotroph", "photoheterotroph")) {
  # import map 
  SVG_template <- read_svg("../data/input/map_central_metabolism_syn.svg")
  
  # set stroke on SVG map
  SVG_mix <- set_attributes(SVG_template,
    node = filter(df_nakajima_mfa, condition == cond)$reaction,
    attr = "style",
    pattern = "stroke-width:[0-9]+\\.[0-9]+",
    replacement = paste0("stroke-width:",
      filter(df_nakajima_mfa, condition == cond)$stroke_width))
  
  # set color
  SVG_mix <- set_attributes(SVG_mix,
    node = filter(df_nakajima_mfa, condition == cond)$reaction,
    attr = "style",
    pattern = "stroke:#b3b3b3",
    replacement = paste0("stroke:",
      filter(df_nakajima_mfa, condition == cond)$stroke_color_rgb))
  
  # set arrow directionality
  SVG_mix <- set_attributes(SVG_mix,
    node = filter(df_nakajima_mfa, condition == cond, flux < 0)$reaction,
    attr = "style",
    pattern = "marker-end:url\\(#marker[0-9]*\\);",
    replacement = "")
  
  SVG_mix <- set_attributes(SVG_mix,
    node = filter(df_nakajima_mfa, condition == cond, flux > 0)$reaction,
    attr = "style",
    pattern = "marker-start:url\\(#marker[0-9]*\\);",
    replacement = "")
  
  write_svg(SVG_mix, file = paste0("../data/output/map_", cond, "y.svg"))
}
Metabolic flux with mixotrophy Metabolic flux with photoheterotrophy

Now we plot fitness of central carbon metabolism genes for two or three selected conditions. These will be added to the metabolic map manually. The mixotrophic conditions LC, LL, +G and HC, LL, +G turned out to be very similar.

df_centralcarb <- tibble(
  locus = c(
    # EMP
    "sll0593", "slr0329", "sll0329", "slr0952", "slr2094",
    "sll0018", "slr0943", "slr0783", "slr0884", "sll1342",
    "slr0394", "slr1945", "slr0752", "sll1275", "sll0587",
    # PPP + CBB
    "slr1843", "sll1479", "slr1349", "slr1793", "sll1070",
    "sll0807", "slr0194", "ssl2153", "sll1525", "slr0012",
    "slr0009",
    # Pyruvate metabolism
    "sll1841", "sll1721", "slr1096", "slr1934", "sll0401",
    "slr0721", "sll0920",
    # TCA
    "slr0665", "slr1289", "slr1096", "sll1023", "sll1557",
    "slr1233", "slr0201", "sll1625", "sll0823", "slr0018",
    "sll0891"),
  reaction = c(
    # EMP
    "HEX", "HEX", "PGI", "FBP", "FBP",
    "FBA", "FBA", "TPI", "GAPDH", "GAPDH",
    "PGK", "PGM", "ENO", "PYK", "PYK",
    # PPP + CBB
    "G6PDH", "PGL", "GND", "TAL", "TKT",
    "RPE", "RPI", "RPI", "PRUK", "RUBISCO",
    "RUBISCO", 
    # Pyruvate metabolism
    "PDH", "PDH", "PDH", "PDH", "CS", 
    "ME", "PPC",
    # TCA
    "ACONT", "ICDH", "AKGDH", "SUCOAS", "SUCOAS",
    "SUCD", "SUCD", "SUCD", "SUCD", "FUM",
    "MDH"
    ))

df_centralcarb <- df_gene %>% filter(
    time == 0,
    condition %in% c("LC, LL", "LC, LL, +G", "LC, LL, +D, +G")) %>%
  inner_join(df_centralcarb, ., by = "locus") %>%
  mutate(sgRNA_target = paste0(reaction, " (", sgRNA_target, ")") %>%
    fct_inorder) %>%
  mutate(condition = recode(condition, `LC, LL` = "Phototrophy", `LC, LL, +G` = "Mixotrophy",
    `LC, LL, +D, +G` = "Photoheterotrophy") %>% factor(., unique(.)[c(1,3,2)])) %>%
  mutate(pathway = rep(c("EMP", "PPP + CBB", "Pyruvate", "TCA"), c(45,33,21,33)))
plot_centralcarb_minifig <- df_centralcarb %>%
  group_by(pathway) %>% group_split %>%
  lapply(function(df) {
    ggplot(df, aes(x = condition, y = wmean_fitness, 
      ymin = wmean_fitness-sd_fitness, 
      ymax = wmean_fitness+sd_fitness, fill = condition, color = condition)) +
    geom_hline(yintercept = c(0, -5, -10), linetype = 3, col = grey(0.6)) +
    geom_col(position = "dodge", width = 0.6) +
    geom_errorbar(position = "dodge", width = 0.6, size = 1) +
    custom_theme(aspect.ratio = 1, legend.position = 0) + 
    theme(axis.text.x = element_blank(), axis.text.y = element_blank(), 
      axis.ticks = element_blank(), panel.grid.major = element_blank(),
      strip.text = element_text(size = 8)) +
    labs(x = "", y = "") +
    coord_cartesian(ylim = c(-11, 1)) +
    scale_fill_manual(values = custom_colors[c(5,2,3)]) +
    scale_color_manual(values = custom_colors[c(5,2,3)]) +
    facet_wrap(~ sgRNA_target, ncol = 11)
  })

ggarrange(nrow = 4, heights =  c(0.4, 0.2, 0.185, 0.2),
  plot_centralcarb_minifig[[1]], plot_centralcarb_minifig[[2]],
  plot_centralcarb_minifig[[3]], plot_centralcarb_minifig[[4]]
)

8.3 Adaptation to light and carbon excess

We will look at three different types of regulatory adaptations:

  • apc/cpcantenna proteins (phycobilisomes), known to be among the most expressed and regulated genes in cyanos
  • flavoproteins Flv1 (sll1521), Flv2 (sll0219), Flv3 (sll0550), Flv4 (sll0217), sll0218 (in flv2/4 operon)
  • low affinity/high flux transporters Ci transporters: bicA (sll0834), NDH-I4 with ndhF4, D4, cupB (sll0026, sll0027, slr1302)
  • high affinity/low flux inducible Ci transporters: BCT1/cmpAB(porB)CD (slr0040-44), SbtA/B (slr1512, slr1513), NDH-I3 with ndhF3, ndhD3, cupA, cupS (sll1732-35)
  • carbon transport regulatory proteins: ccmR/rbcR (sll1594), cmpR (sll0030), cyabrB1 (sll0359), cyabrB2 (sll0822)
plot_phycobilisome <- df_gene %>% filter(str_detect(gene_name, "[ac]pc[ABCDEFG]")) %>%
  plot_gene_fitness(ncol = 6, legend.position = 0)

plot_flv_genes <- df_gene %>% filter(locus %in% c("sll1521", "sll0219", "sll0550", "sll0217", "sll0218")) %>%
  mutate(sgRNA_target = recode(sgRNA_target, `sll1521` = "Flv1 (sll1521)", `sll0219` = "Flv2 (sll0219)",
    `sll0550` = "Flv3 (sll0550)", `sll0217` = "Flv4 (sll0217)")) %>%
  mutate(sgRNA_target = factor(sgRNA_target, c(unique(sgRNA_target), ""))) %>%
  plot_gene_fitness(ncol = 6, legend.position = 0)

plot_carbon_uptake <- df_gene %>% filter(locus %in% c(
    "sll0026", "sll0027", "slr1302",
    "sll1732", "sll1733", "sll1734", "sll1735", "slr0040", "slr0041","slr0043","slr0044"
  )) %>%
  mutate(sgRNA_target = recode(sgRNA_target,
    `nrtC2` = "cmpC", `nrtD3` = "cmpD",
    `sll1734` = "cupA", `slr1302` = "cupB",
    `sll1735` = "cupS", `ndhF2` = "ndhF3"
  )) %>%
  mutate(sgRNA_target = factor(sgRNA_target, unique(sgRNA_target)[c(4,6,11,3,5,9,10,1,2,7,8)])) %>%
  plot_gene_fitness(ncol = 6, legend.position = 0) +
  coord_cartesian(ylim = c(-7.9, 2.4))

Figure 3 draft:

ggarrange(nrow = 3, heights =  c(0.47, 0.2, 0.33), labels = LETTERS[1:3], font.label = list_fontpars,
  plot_phycobilisome,
  plot_flv_genes,
  plot_carbon_uptake
)

As a Supplementary figure to C), we can plot all other carbon transporters and regulatory genes that showed a less remarkable effect.

plot_carbon_uptake_2 <- df_gene %>% filter(locus %in% c(
    "sll0834", "slr1512", "slr1513", "sll1594", "sll0030", "sll0359", "sll0822"
  )) %>%
  mutate(sgRNA_target = recode(sgRNA_target,
    `sll0834` = "bicA", `slr1512` = "sbtA", `slr1513` = "sbtB",
    `sll0359` = "cyabrB1", `sll0822` = "cyabrB2", `rbcR` = "ccmR"
  )) %>%
  mutate(sgRNA_target = factor(sgRNA_target, unique(sgRNA_target))) %>%
  plot_gene_fitness(ncol = 4, legend.position = "right")

plot_carbon_uptake_2

As another Supplementary Figure, we can plot the total protein mass of the phycobilisome determined by protein mass spectrometry. This data was published in our study Jahn et al., Cell Reports, 2018. The data can be downloaded directly from the ShinyProt github page where it is included for on demand visualization.

load(url("https://github.com/m-jahn/ShinyProt/blob/master/data/Jahn_2018_Light_and_CO2_lim.Rdata?raw=true"))

plot_protmass_phycobilisome1 <- Jahn_2018_Light_and_CO2_lim %>%
  filter(str_detect(protein, "[ac]pc[ABCDEFG]"), sample != "CO2") %>%
  mutate(protein = str_extract(protein, "[ac]pc[ABCDEFG][12]?")) %>%
  ggplot(aes(x = factor(light), y = 100*mean_mass_fraction_norm, 
  fill = str_sub(protein, 1, 3),
  label = if_else(str_detect(protein, "[ac]pc[C-Z][12]?"), "", protein))) +
  lims(y = c(0, 22)) +
  geom_col(position = "stack", width = 0.7, col = grey(1), size = 0.2) +
  geom_text(size = 2.5, position = position_stack(vjust = 0.5), color = "white") +
  custom_theme(legend.position = "bottom", legend.key.size = unit(0.5, "cm")) +
  labs(title = "Light limitation", x = expression("µmol photons m"^-2*" s"^-1), y = "% protein mass") +
  scale_fill_manual(values = c("#8eb655", "#937fb3")) +
  scale_color_manual(values = c("#8eb655", "#937fb3"))

plot_protmass_phycobilisome2 <- Jahn_2018_Light_and_CO2_lim %>%
  filter(str_detect(protein, "[ac]pc[ABCDEFG]"), sample == "CO2") %>%
  mutate(protein = str_extract(protein, "[ac]pc[ABCDEFG][12]?")) %>%
  ggplot(aes(x = factor(co2_concentration), y = 100*mean_mass_fraction_norm, 
  fill = str_sub(protein, 1, 3),
  label = if_else(str_detect(protein, "[ac]pc[C-Z][12]?"), "", protein))) +
  lims(y = c(0, 22)) +
  geom_col(position = "stack", width = 0.7, col = grey(1), size = 0.2) +
  geom_text(size = 2.5, position = position_stack(vjust = 0.5), color = "white") +
  custom_theme(legend.position = "bottom", legend.key.size = unit(0.5, "cm")) +
  labs(title = "CO2 limitation", x = "% CO2 in air", y = "% protein mass") +
  scale_fill_manual(values = c("#8eb655", "#937fb3")) +
  scale_color_manual(values = c("#8eb655", "#937fb3"))

ggarrange(ncol = 2, widths = c(0.5,0.5),
  labels = LETTERS[1:2], font.label = list_fontpars,
  plot_protmass_phycobilisome1,
  plot_protmass_phycobilisome2
)

Other genes of interest that either did not show any (remarkable) effect on fitness, or do not meet the scope of this section:

  • OCP (slr1963), pgr5 (ssr2016)
  • SigB (sll0306), SigC (sll0184), SigD (sll2012), SigE (sll1689) (rpoD genes 1-4)
  • ccmM (sll1031), ccmK2 (sll1028), ccmK1 (sll1029), ccmN (sll1032), ccmO (slr0436), ccmL (sll1030)
  • CP12 (ssl3364)

8.4 Genes where knock down leads to increased fitness

list_genes_pos_fitness <- df_gene %>%
  filter(time == 0, !is.na(locus), wmean_fitness > 2) %>%
  pull(locus) %>% unique

plot_gene_fitness(df_gene, gene = list_genes_pos_fitness, title = "Genes with increased fitness (f > 2)")
Warning: Removed 11 rows containing missing values (geom_text).

Summary: - pmgA is once again the gene with strongest and most widespread fitness increase, validating results from library V1 - slr1916 same phenotype as pmgA just weaker. We also know this one from before. Must have identical role as pmgA. - all PSII genes show increased fitness in photoheterotrophic condition –> PS is a burden here - sll0689, pxcA, slr1609 - all increased fitness in HC,HL, first two are Na+/CO2 (?) trnasporters, slr1609 we know from before, annotated as fatty acid CoA ligase, but probably it’s something different - sll6055, slr1505, slr1990 - all increased fitness in photoheterotrophic condition, and decreased fitness in HC/LL conditions. Not much is known about these genes, probably a role in photosynthesis, as the pattern is similar to psb genes (PSII maturation?) - slr0813, slr0907, slr909, slr1299 - all increased fitness in HC/LL. Not clear what connects these genes functionally.

9 Differential fitness of non-coding RNAs (ncRNAs)

9.2 Antisense RNAs and iTSSs

The first part of a more detailed analysis is to extract asRNAs and iTSSs with differential fitness, and compare them to their associated genes. The assumption is that sgRNAs targeting asRNAs/iTSSs in reality repress transcription of their parent genes, and by these means produce a fitness effect that can not be attributed to the action of the asRNA itself. The first step is filter the ncRNA dataset and order ncRNAs by fitness similarity. The complete figure for the analysis follows at the end of the chapter.

df_ncRNA_select <- df_ncRNA %>%
  filter(time == 0) %>%
  group_by(sgRNA_target) %>%
  filter(any(comb_score >= 4)) %>%
  ungroup

Plot asRNA vs gene fitness.

plot_asRNA_xy <- df_ncRNA_select %>% filter(ncRNA_type == "asRNA") %>%
  left_join(by = c("condition", "locus"),
    select(df_gene, locus, condition, wmean_fitness, sd_fitness) %>% distinct %>%
    rename(gene_fitness = wmean_fitness, sd_gene_fitness = sd_fitness)) %>%
  select(locus, condition, wmean_fitness, gene_fitness) %>%
  mutate(locus = if_else(locus %in% c("sll1773", "slr1609"), locus, "other")) %>%
  ggplot(aes(x = wmean_fitness, y = gene_fitness, color = locus)) +
  geom_abline(intercept = 0, slope = 1, lty = 2) +
  geom_abline(intercept = 4, slope = 1, lty = 2) +
  geom_abline(intercept = -4, slope = 1, lty = 2) +
  geom_point(alpha = 0.5, size = 1.5) +
  ggpubr::stat_cor() +
  coord_cartesian(xlim = c(-9, 5), ylim = c(-9, 5)) +
  custom_theme(legend.pos = c(0.8, 0.15), legend.key.size = unit(0.2, "cm")) +
  labs(x = "asRNA fitness", y = "gene fitness") +
  scale_color_manual(values = custom_colors[c(5,1:2)])

Plot iTSS fitness versus gene fitness.

plot_iTSS_xy <- df_ncRNA_select %>% filter(ncRNA_type == "iTSS") %>%
  left_join(by = c("condition", "locus"),
    select(df_gene, locus, condition, wmean_fitness, sd_fitness) %>% distinct %>%
    rename(gene_fitness = wmean_fitness, sd_gene_fitness = sd_fitness)) %>%
  select(locus, condition, wmean_fitness, gene_fitness) %>%
  mutate(locus = if_else(locus %in% c("sll1406", "slr1415", "sll1542", "sll1558"), locus, "other")) %>%
  ggplot(aes(x = wmean_fitness, y = gene_fitness, color = locus)) +
  geom_abline(intercept = 0, slope = 1, lty = 2) +
  geom_abline(intercept = 4, slope = 1, lty = 2) +
  geom_abline(intercept = -4, slope = 1, lty = 2) +
  geom_point(alpha = 0.5, size = 1.5) +
  ggpubr::stat_cor() +
  coord_cartesian(xlim = c(-9, 5), ylim = c(-9, 5)) +
  custom_theme(legend.pos = c(0.8, 0.15), legend.key.size = unit(0.2, "cm")) +
  labs(x = "iTSS fitness", y = "gene fitness") +
  scale_color_manual(values = custom_colors[c(5,1:4)])
ggarrange(nrow = 2, labels = c("A"), font.label = list_fontpars,
  plot_ncRNA_overview,
  ggarrange(ncol = 2, labels = c("B", "C"), font.label = list_fontpars,
    plot_asRNA_xy, plot_iTSS_xy)
)
Warning: Removed 11 rows containing non-finite values (stat_cor).
Warning: Removed 11 rows containing missing values (geom_point).

9.3 noncoding RNAs as regulatory elements

The second part of this analysis is to look at non-gene associated (intergenic) ncRNA elements. Of these, several are known to have a regulatory effect. We import an alignment that was done in python using the biopython package. All ncRNAs were aligned to the Synechocystis genome, and we can now extract the genome positions to retrieve the neighboring genes.

The first step is to parse the (important) information of the alignment text file into a table.

df_annotation <- read_csv("../../sgRNA_library/raw_data/Synechocystis_PCC6803_genome_annotation_20190614.csv", col_types = cols()) %>%
  arrange(start_bp)

df_alignment <- read_lines("../data/output/ncRNA_alignment.txt") %>%
  as_tibble %>%
  mutate(name = rep(c("locus", "genome", "genome_length", "score", "seq", "alignment", "genome_pos", "blank"), length.out = n())) %>%
  pivot_wider(names_from = "name", values_from = "value") %>%
  tidyr::unchop(cols = everything()) %>%
  mutate(locus = str_remove(locus, "alignment of: ")) %>%
  filter(str_detect(genome, "47118304")) %>%
  select(-genome_length, -score, -seq, -alignment, -blank) %>%
  mutate(
    genome_start = str_extract(genome_pos, "Sbjct: +[0-9]*") %>%
    str_extract("[0-9]+") %>% as.numeric) %>%
  mutate(
    genome_end = str_extract(genome_pos, "[ATCG] +[0-9]*") %>%
    str_extract("[0-9]+") %>% as.numeric) %>%
  mutate(length = abs(genome_end-genome_start))
Warning: Values from `value` are not uniquely identified; output will contain list-cols.
* Use `values_fn = list` to suppress this warning.
* Use `values_fn = {summary_fun}` to summarise duplicates.
* Use the following dplyr code to identify duplicates.
  {data} %>%
    dplyr::group_by(name) %>%
    dplyr::summarise(n = dplyr::n(), .groups = "drop") %>%
    dplyr::filter(n > 1L)
head(df_alignment)

The second step is to look up the 5’ and 3’ neighboring genes for each ncRNA. One could also do this manually but it becomes impractical for something like 10+ ncRNA loci. After obtaining a table with both the ncRNA in one column and its associated 5’ and 3’ neighboring genes in separate columns, we can make a summary fitness table and plot fitness of ncRNA and its neighbors against each other.

# get neighboring genes
df_alignment <- df_alignment %>%
  bind_cols(
    sapply(df_alignment$genome_start, function(x) {
      df = filter(df_annotation, location == "Chr")
      neighbors = get_neighbors(n = df$start_bp, threshold = x, type = "pos")
      unlist(as.list(df[neighbors, "GeneID"]))
    }) %>% t %>% as_tibble
  )

# some join operations to merge different fitness data in one DF
df_ncRNA_comp <- df_ncRNA_select %>% filter(ncRNA_type == "ncRNA") %>%
  left_join(rename(df_alignment, sgRNA_target = locus, upstream = GeneID1, downstream = GeneID2) %>%
    select(sgRNA_target, upstream, downstream),
    by = "sgRNA_target") %>%
  left_join(df_gene %>% select(locus, condition, wmean_fitness) %>%
    distinct %>% rename(upstream = locus, wmean_fitness_us = wmean_fitness),
    by = c("condition", "upstream")
  ) %>%
  left_join(df_gene %>% select(locus, condition, wmean_fitness) %>%
    distinct %>% rename(downstream = locus, wmean_fitness_ds = wmean_fitness),
    by = c("condition", "downstream")
  )

# get a list of interesting ncRNAs where the neighboring gene
# has NO effect on fitness
list_ncRNA_hits <- df_ncRNA_comp %>%
  filter(
    abs(wmean_fitness - wmean_fitness_us) >= 4 &
    abs(wmean_fitness - wmean_fitness_ds) >= 4
  ) %>% group_by(sgRNA_target) %>% count %>%
  filter(n > 1) %>%
  pull(sgRNA_target)

The third step is to plot a heat map and the comparison of upstream and downstream gene fitness.

plot_ncRNA_heat <- df_ncRNA_select %>% filter(ncRNA_type == "ncRNA") %>%
  mutate(sgRNA_target = fct_cluster(sgRNA_target, condition, wmean_fitness)) %>%
  mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
  ggplot(aes(x = condition, y = sgRNA_target, fill = wmean_fitness)) +
  geom_tile() + custom_theme(legend.pos = "bottom", legend.key.size = unit(0.4, "cm"), legend.margin = unit(0, "cm")) +
  labs(x = "", y = "") +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
  scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
    limits = c(-4, 4))
Warning: `legend.margin` must be specified using `margin()`. For the old behavior use legend.spacing
# check correlation of ncRNA fitness with upstream lying genes
plot_ncRNA_upstream <- df_ncRNA_comp %>%
  mutate(sgRNA_target = if_else(locus %in% list_ncRNA_hits, sgRNA_target, "other")) %>%
  arrange(desc(sgRNA_target)) %>%
  ggplot(aes(x = wmean_fitness, y = wmean_fitness_us, color = sgRNA_target)) +
  geom_abline(intercept = 0, slope = 1, lty = 2) +
  geom_abline(intercept = 4, slope = 1, lty = 2) +
  geom_abline(intercept = -4, slope = 1, lty = 2) +
  geom_point(alpha = 0.5, size = 1.5) +
  ggpubr::stat_cor() +
  coord_cartesian(xlim = c(-9, 5), ylim = c(-9, 5)) +
  custom_theme(legend.pos = c(0.8, 0.2), legend.key.size = unit(0.2, "cm")) +
  labs(x = "ncRNA fitness", y = "upstream gene fitness") +
  scale_color_manual(values = custom_colors[c(1:2,5)])

# check correlation of ncRNA fitness with downstream lying genes
plot_ncRNA_downstream <- df_ncRNA_comp %>%
  mutate(sgRNA_target = if_else(locus %in% list_ncRNA_hits, sgRNA_target, "other")) %>%
  arrange(desc(sgRNA_target)) %>%
  ggplot(aes(x = wmean_fitness, y = wmean_fitness_ds, color = sgRNA_target)) +
  geom_abline(intercept = 0, slope = 1, lty = 2) +
  geom_abline(intercept = 4, slope = 1, lty = 2) +
  geom_abline(intercept = -4, slope = 1, lty = 2) +
  geom_point(alpha = 0.5, size = 1.5) +
  ggpubr::stat_cor() +
  coord_cartesian(xlim = c(-9, 5), ylim = c(-9, 5)) +
  custom_theme(legend.pos = c(0.8, 0.2), legend.key.size = unit(0.2, "cm")) +
  labs(x = "ncRNA fitness", y = "downstream gene fitness") +
  scale_color_manual(values = custom_colors[c(1:2,5)])

Main text figure for ncRNAs only.

ggarrange(ncol = 2, labels = c("A", "B"), font.label = list_fontpars,
  plot_ncRNA_heat + theme(plot.margin = unit(c(12, 12, -20, 12), "points")),
  ggarrange(nrow = 3, heights = c(0.43, 0.43, 0.14),
    labels = c("", "C", ""), font.label = list_fontpars,
    plot_ncRNA_upstream,
    plot_ncRNA_downstream
  )
)

10 Export summary table of all genes and conditions

Export a summary table of all genes and conditions, so that it’s easy for other people to look up single conditions as for example done in one-by-one fitness comparisons. This is best done in wide format (one column per condition).

df_gene %>% ungroup %>%
  filter(sgRNA_type == "gene") %>%
  select(locus, sgRNA_target, gene_name, condition, wmean_fitness) %>% 
  distinct %>%
  pivot_wider(names_from = condition, values_from = wmean_fitness) %>%
  write_csv("../data/output/fitness_summary.csv")

df_gene %>%
  filter(sgRNA_type == "gene") %>%
  write_csv("../data/output/fitness_genes.csv")

df_kegg %>% write_csv("../data/output/kegg_annotation.csv")

The entire pipeline takes about 25 minutes to run on a standard notebook. To work on single sections, the work space is exported to avoid constant recalculation of result tables.

11 Session Info

sessionInfo()
R version 4.2.0 (2022-04-22)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Linux Mint 20

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=sv_SE.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=sv_SE.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=sv_SE.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=sv_SE.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] fluctuator_0.1.0    ggpubr_0.4.0        kableExtra_1.3.4    corrplot_0.92       limma_3.48.3        KEGGREST_1.32.0    
 [7] tsne_0.1-3.1        vegan_2.6-2         permute_0.9-7       dendextend_1.15.2   scales_1.2.0        latticetools_0.1.0 
[13] latticeExtra_0.6-29 lattice_0.20-45     ggrepel_0.9.1       forcats_0.5.1       stringr_1.4.0       dplyr_1.0.8        
[19] purrr_0.3.4         readr_2.1.2         tidyr_1.2.0         tibble_3.1.6        ggplot2_3.3.5       tidyverse_1.3.1    

loaded via a namespace (and not attached):
 [1] colorspace_2.0-3       ggsignif_0.6.3         ellipsis_0.3.2         rsconnect_0.8.25       XVector_0.32.0        
 [6] fs_1.5.2               rstudioapi_0.13        farver_2.1.0           bit64_4.0.5            fansi_1.0.3           
[11] lubridate_1.8.0        xml2_1.3.3             splines_4.2.0          knitr_1.38             jsonlite_1.8.0        
[16] broom_0.8.0            cluster_2.1.3          dbplyr_2.1.1           png_0.1-7              compiler_4.2.0        
[21] httr_1.4.2             backports_1.4.1        assertthat_0.2.1       Matrix_1.4-1           fastmap_1.1.0         
[26] cli_3.2.0              htmltools_0.5.2        tools_4.2.0            gtable_0.3.0           glue_1.6.2            
[31] GenomeInfoDbData_1.2.6 Rcpp_1.0.8.3           carData_3.0-5          jquerylib_0.1.4        cellranger_1.1.0      
[36] vctrs_0.4.1            Biostrings_2.60.2      svglite_2.1.0          nlme_3.1-157           xfun_0.30             
[41] rvest_1.0.2            lifecycle_1.0.1        rstatix_0.7.0          XML_3.99-0.9           directlabels_2021.1.13
[46] MASS_7.3-56            zlibbioc_1.38.0        vroom_1.5.7            hms_1.1.1              parallel_4.2.0        
[51] RColorBrewer_1.1-3     yaml_2.3.5             gridExtra_2.3          sass_0.4.1             stringi_1.7.6         
[56] S4Vectors_0.30.2       BiocGenerics_0.38.0    GenomeInfoDb_1.28.4    rlang_1.0.2            pkgconfig_2.0.3       
[61] systemfonts_1.0.4      bitops_1.0-7           evaluate_0.15          labeling_0.4.2         bit_4.0.4             
[66] cowplot_1.1.1          tidyselect_1.1.2       magrittr_2.0.3         R6_2.5.1               IRanges_2.26.0        
[71] generics_0.1.2         DBI_1.1.2              pillar_1.7.0           haven_2.5.0            withr_2.5.0           
[76] mgcv_1.8-40            abind_1.4-5            RCurl_1.98-1.6         modelr_0.1.8           crayon_1.5.1          
[81] car_3.0-12             utf8_1.2.2             tzdb_0.3.0             rmarkdown_2.13         viridis_0.6.2         
[86] jpeg_0.1-9             readxl_1.4.0           reprex_2.0.1           digest_0.6.29          webshot_0.5.3         
[91] stats4_4.2.0           munsell_0.5.0          viridisLite_0.4.0      bslib_0.3.1            quadprog_1.5-8        
LS0tCnRpdGxlOiAiQ1JJU1BSaSBsaWJyYXJ5IFYyLCBkYXRhIHByb2Nlc3NpbmcgcGlwZWxpbmUiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IGNvc21vCiAgICB0b2M6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCi0tLS0tLS0tLS0KCiMgRGVzY3JpcHRpb24KClRoaXMgUiBub3RlYm9vayBkZXRhaWxzIHRoZSBkYXRhIHByb2Nlc3NpbmcgYW5kIHZpc3VhbGl6YXRpb24gZm9yIGdyb3d0aCBjb21wZXRpdGlvbiBleHBlcmltZW50cyB3aXRoIGEgQ1JJU1BSaSBzZ1JOQSBsaWJyYXJ5LiBUaGUgbGlicmFyeSBjb250YWlucyBhcm91bmQgMjAsMDAwIHVuaXF1ZSBzZ1JOQSByZXByZXNzaW9uIG11dGFudHMgdGFpbG9yZWQgZm9yIHRoZSBjeWFub2JhY3Rlcml1bSBfU3luZWNob2N5c3Rpc18gc3AuIFBDQzY4MDMuIFRoaXMgbGlicmFyeSBpcyB0aGUgc2Vjb25kIHZlcnNpb24gKHRoZXJlZm9yZSAiVjIiKSBvZiBhbiBzZ1JOQSBsaWJyYXJ5IGZvciBfU3luZWNob2N5c3Rpc18sIGNvbnRhaW5pbmcgZml2ZSBpbnN0ZWFkIG9mIG9ubHkgdHdvIHNnUk5BcyBwZXIgZ2VuZS4gSW4gc29tZSBjYXNlcywgZ2VuZXMgb3IgbmNSTkFzIGFyZSBzbyBzaG9ydCB0aGF0IGl0IGlzIG5vdCBwb3NzaWJsZSB0byBkZXNpZ24gYSBtYXhpbXVtIG9mIGZpdmUgaW5kaXZpZHVhbCBzZ1JOQXMuCgpUaGUgZmlyc3QgaXRlcmF0aW9uIG9mIHRoZSBfU3luZWNob2N5c3Rpc18gc2dSTkEgbGlicmFyeSB3YXMgW3B1Ymxpc2hlZCBpbiBOYXR1cmUgQ29tbXVuaWNhdGlvbnMsIDIwMjBdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNDY3LTAyMC0xNTQ5MS03KS4KCiMgUHJlcmVxdWlzaXRlcwoKTG9hZCByZXF1aXJlZCBwYWNrYWdlcy4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UgfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkodGlkeXZlcnNlKQogIGxpYnJhcnkoZ2dyZXBlbCkKICBsaWJyYXJ5KGxhdHRpY2UpCiAgbGlicmFyeShsYXR0aWNlRXh0cmEpCiAgbGlicmFyeShsYXR0aWNldG9vbHMpCiAgbGlicmFyeShzY2FsZXMpCiAgbGlicmFyeShkZW5kZXh0ZW5kKQogIGxpYnJhcnkodmVnYW4pCiAgbGlicmFyeSh0c25lKQogIGxpYnJhcnkoS0VHR1JFU1QpCiAgbGlicmFyeShsaW1tYSkKICBsaWJyYXJ5KGNvcnJwbG90KQogIGxpYnJhcnkoa2FibGVFeHRyYSkKICBsaWJyYXJ5KGdyaWQpCiAgbGlicmFyeShnZ3B1YnIpCn0pCmBgYAoKRGVmaW5lIGdsb2JhbCBmaWd1cmUgc3R5bGUsIGRlZmF1bHQgY29sb3JzLCBhbmQgYSBwbG90IHNhdmluZyBmdW5jdGlvbi4KCmBgYHtyLCBlY2hvID0gRkFMU0V9CiMgY3VzdG9tIGdncGxvdDIgdGhlbWUgdGhhdCBpcyByZXVzZWQgZm9yIGFsbCBsYXRlciBwbG90cwpjdXN0b21fY29sb3JzID0gYygiI0U3Mjk4QSIsICIjNjZBNjFFIiwgIiNFNkFCMDIiLCAiIzc1NzBCMyIsICIjQjNCM0IzIiwgIiMxQjlFNzciLCAiI0Q5NUYwMiIsICIjQTY3NjFEIikKY3VzdG9tX3JhbmdlIDwtIGZ1bmN0aW9uKG4gPSA1KSB7Y29sb3JSYW1wUGFsZXR0ZShjdXN0b21fY29sb3JzW2MoMSw1LDIpXSkobil9CgpjdXN0b21fdGhlbWUgPC0gZnVuY3Rpb24oYmFzZV9zaXplID0gMTIsIGJhc2VfbGluZV9zaXplID0gMS4wLCBiYXNlX3JlY3Rfc2l6ZSA9IDEuMCwgLi4uKSB7CiAgdGhlbWVfbGlnaHQoYmFzZV9zaXplID0gYmFzZV9zaXplLCBiYXNlX2xpbmVfc2l6ZSA9IGJhc2VfbGluZV9zaXplLCBiYXNlX3JlY3Rfc2l6ZSA9IGJhc2VfcmVjdF9zaXplKSArIHRoZW1lKAogICAgdGl0bGUgPSBlbGVtZW50X3RleHQoY29sb3VyID0gZ3JleSgwLjQpLCBzaXplID0gMTApLAogICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsMTIsMTIsMTIpLCAicG9pbnRzIiksCiAgICBheGlzLnRpY2tzLmxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSwKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JleSgwLjQpLCBsaW5ldHlwZSA9ICJzb2xpZCIsIGxpbmVlbmQgPSAicm91bmQiKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IGdyZXkoMC40KSwgc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IGdyZXkoMC40KSwgc2l6ZSA9IDEwKSwKICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNiwgbGluZXR5cGUgPSAic29saWQiLCBjb2xvdXIgPSBncmV5KDAuOSkpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChsaW5ldHlwZSA9ICJzb2xpZCIsIGNvbG91ciA9IGdyZXkoMC40KSwgZmlsbCA9IE5BLCBzaXplID0gMS4wKSwKICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSBncmV5KDAuNCksIHNpemUgPSAxMCwgbWFyZ2luID0gdW5pdChyZXAoMyw0KSwgInBvaW50cyIpKSwKICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IGdyZXkoMC40KSwgc2l6ZSA9IDEwKSwKICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgLi4uCiAgKQp9CgojIHNldCBncmFwaGljYWwgcGFyYW1ldGVyIGZvciBzdWJmaWd1cmUgbGFiZWxzCmxpc3RfZm9udHBhcnMgPC0gbGlzdChmYWNlID0gInBsYWluIiwgc2l6ZSA9IDE0KQoKIyBmdW5jdGlvbiB0byBleHBvcnQgYW4gaW1hZ2UgYXMgc3ZnIGFuZCBwbmcKc2F2ZV9wbG90IDwtIGZ1bmN0aW9uKHBsLCBwYXRoID0gIi4uL2ZpZ3VyZXMvIiwgd2lkdGggPSA2LCBoZWlnaHQgPSA2KSB7CiAgcGxfbmFtZSA8LSBkZXBhcnNlKHN1YnN0aXR1dGUocGwpKQogIHN2ZyhmaWxlbmFtZSA9IHBhc3RlMChwYXRoLCBwbF9uYW1lLCAiLnN2ZyIpLAogICAgd2lkdGggPSB3aWR0aCwgaGVpZ2h0ID0gaGVpZ2h0KQogIHByaW50KHBsKQogIGRldi5vZmYoKQogIHBuZyhmaWxlbmFtZSA9IHBhc3RlMChwYXRoLCBwbF9uYW1lLCAiLnBuZyIpLAogICAgd2lkdGggPSB3aWR0aCoxMjUsIGhlaWdodCA9IGhlaWdodCoxMjUsIHJlcyA9IDEyMCkKICBwcmludChwbCkKICBpbnZpc2libGUoY2FwdHVyZS5vdXRwdXQoZGV2Lm9mZigpKSkKfQpgYGAKCgojIFF1YWxpdHkgY29udHJvbAoKIyMgRGF0YSBpbXBvcnQKCkxvYWQgcmF3IGRhdGEuIFRoZSBtYWluIHRhYmxlIGNvbnRhaW5zIGFscmVhZHkgbm9ybWFsaXplZCBxdWFudGlmaWNhdGlvbiBvZiBhbGwgc2dSTkFzLCBmb2xkIGNoYW5nZSwgbXVsdGlwbGUgaHlwb3RoZXNpcyBjb3JyZWN0ZWQgcC12YWx1ZXMsIGFuZCBmaXRuZXNzIHNjb3JlcyBvbiB0aGUgZ3VpZGUgUk5BIGFuZCBnZW5lIGxldmVsLiBDb250cmFyeSB0byB0aGUgcHJvY2Vzc2luZyBvZiBbb3VyIGZpcnN0IENSSVNQUmkgbGlicmFyeSBWMV0oaHR0cHM6Ly9naXRodWIuY29tL20tamFobi9SLW5vdGVib29rLWNyaXNwcmktbGliI2NvbnRlbnRzKSwgbXVjaCBvZiB0aGUgZnVuY3Rpb25hbGl0eSBmcm9tIHRoZSBub3RlYm9vayB3YXMgdHJhbnNmZXJyZWQgaW50byBhIG5ldyBbTkV4dGZsb3cgQ1JJU1BSaSBsaWJyYXJ5IHBpcGVsaW5lXShodHRwczovL2dpdGh1Yi5jb20vbS1qYWhuL25mLWNvcmUtY3Jpc3ByaXNjcmVlbikgYXZhaWxhYmxlIG9uIGdpdGh1Yi4KCmBgYHtyfQpsb2FkKCIuLi9kYXRhL2lucHV0L3Jlc3VsdC5SZGF0YSIpCmRmX21haW4gPC0gREVTZXFfcmVzdWx0X3RhYmxlCnJtKERFU2VxX3Jlc3VsdF90YWJsZSkKYGBgCgoKIyMgRGF0YSBhbm5vdGF0aW9uCgpEaWZmZXJlbnQgYW5ub3RhdGlvbiBjb2x1bW5zIGFyZSBhZGRlZCB0byB0aGUgbWFpbiBkYXRhIGZyYW1lLCBpbmNsdWRpbmcgYSBzaG9ydCBzZ1JOQSBpZGVudGlmaWVyIChleGNsdWRpbmcgdGhlIHBvc2l0aW9uIG9uIHRoZSBnZW5lKSwgYW4gc2dSTkEgaW5kZXggKDEgdG8gNSksIGFuZCBnZW5vbWUgYW5ub3RhdGlvbiBmcm9tIFVuaXByb3QuIFRoZSBVbmlwcm90IGRhdGEgaXMgZHluYW1pY2FsbHkgZG93bmxvYWRlZCBmb3IgZXZlcnkgdXBkYXRlIG9mIHRoaXMgcGlwZWxpbmUgdXNpbmcgdGhlaXIgdmVyeSBzaW1wbGUgQVBJIChgcmVhZF90c3YoImh0dHBzOi8vd3d3LnVuaXByb3Qub3JnL3VuaXByb3QvP3F1ZXJ5PXRheG9ub215OjExMTE3MDgmZm9ybWF0PXRhYiIpYCkuIFRoZSBmdWxsIGxpc3Qgb2YgY29sdW1ucyB0aGF0IGNhbiBiZSBxdWVyaWVkIGlzIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly93d3cudW5pcHJvdC5vcmcvaGVscC91bmlwcm90a2JfY29sdW1uX25hbWVzKS4KUGF0aHdheSBhbm5vdGF0aW9uIGZyb20gS0VHRyBpcyBsYXRlciBpbiB0aGUgcGlwZWxpbmUgYWRkZWQgdXNpbmcgdGhlIGBLRUdHUkVTVGAgcGFja2FnZS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRmX21haW4gPC0gZGZfbWFpbiAlPiUKICBncm91cF9ieShzZ1JOQV90YXJnZXQpICU+JQogIG11dGF0ZShzZ1JOQV90eXBlID0gaWZfZWxzZShncmVwbCgiXm5jXyIsIHNnUk5BKSwgIm5jUk5BIiwgImdlbmUiKSkgJT4lCiAgdW5ncm91cCAlPiUKICAKICAjIG1hcCB0cml2aWFsIG5hbWVzIHRvIExvY3VzVGFncyB1c2luZyBhIG1hbnVhbGx5IGN1cmF0ZWQgbGlzdAogIGxlZnRfam9pbigKICAgIHJlYWRfdHN2KCIuLi9kYXRhL2lucHV0L21hcHBpbmdfdHJpdmlhbF9uYW1lcy50c3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpLAogICAgYnkgPSBjKCJzZ1JOQV90YXJnZXQiID0gImdlbmUiKSkgJT4lCgogICMgc3BsaXQgY29uZGl0aW9uIGludG8gc2VwYXJhdGUgY29scwogIHNlcGFyYXRlKGNvbmRpdGlvbiwgaW50byA9IGMoImNhcmJvbiIsICJsaWdodCIsICJ0cmVhdG1lbnRfMSIsICJ0cmVhdG1lbnRfMiIpLAogICAgc2VwID0gIiwgIiwgcmVtb3ZlID0gRkFMU0UsIGZpbGwgPSAicmlnaHQiKSAlPiUKICB1bml0ZSgidHJlYXRtZW50IiwgdHJlYXRtZW50XzEsIHRyZWF0bWVudF8yLCBzZXAgPSAiLCAiLCBuYS5ybSA9IFRSVUUpCmBgYAoKT3ZlcnZpZXcgYWJvdXQgdGhlIGRpZmZlcmVudCBjb25kaXRpb25zLgoKYGBge3J9CmRmX2N1bHRpdmF0aW9uX3N1bW1hcnkgPC0gZGZfbWFpbiAlPiUgZ3JvdXBfYnkoY29uZGl0aW9uKSAlPiUKICBzdW1tYXJpemUoCiAgICB0aW1lX3BvaW50cyA9IHBhc3RlKHVuaXF1ZSh0aW1lKSwgY29sbGFwc2UgPSAiLCAiKSwKICAgIGNhcmJvbiA9IHVuaXF1ZShjYXJib24pLAogICAgbGlnaHQgPSB1bmlxdWUobGlnaHQpLAogICAgdHJlYXRtZW50ID0gdW5pcXVlKHRyZWF0bWVudCksCiAgICBtaW5fZml0ID0gbWluKGZpdG5lc3MpLAogICAgbWVkX2ZpdCA9IG1lZGlhbihmaXRuZXNzKSwKICAgIG1heF9maXQgPSBtYXgoZml0bmVzcykpCgpwcmludChkZl9jdWx0aXZhdGlvbl9zdW1tYXJ5KQp3cml0ZV9jc3YoZGZfY3VsdGl2YXRpb25fc3VtbWFyeSwgZmlsZSA9ICIuLi9kYXRhL291dHB1dC9jdWx0aXZhdGlvbl9zdW1tYXJ5LmNzdiIpCmBgYAoKUmV0cmlldmUgZ2VuZSBpbmZvIGZyb20gdW5pcHJvdCBhbmQgbWVyZ2Ugd2l0aCBtYWluIGRhdGEgZnJhbWUuIFdlIG5lZWQgdG8gbWFrZSBhIGN1c3RvbSBmdW5jdGlvbiB0byByZXRyaWV2ZSBhbmQgcGFyc2UgdGhlIGRhdGEgZnJvbSB1bmlwcm90LCBiZWNhdXNlIG9mIGEgYnVnIGluIHRoZSBzZWN1cml0eSBsZXZlbCBvbiBVYnVudHUgMjAuMDQuIFRoZSBmYWxsYmFjayBvcHRpb24gaXMgdG8gbG9hZCBhIGxvY2FsIGNvcHkgb2YgdW5pcHJvdCBhbm5vdGF0aW9uIGZvciB0aGlzIG9yZ2FuaXNtLgoKYGBge3J9CmxpYnJhcnkoaHR0cikKdW5pcHJvdF91cmwgPC0gcGFzdGUwKAogICAiaHR0cHM6Ly93d3cudW5pcHJvdC5vcmcvdW5pcHJvdC8/cXVlcnk9dGF4b25vbXk6MTExMTcwOCZmb3JtYXQ9dGFiJiIsCiAgICJjb2x1bW5zPWlkLGdlbmVzLGdlbmVzKFBSRUZFUlJFRCkscHJvdGVpbl9uYW1lcyxsZW5ndGgsbWFzcyxlYyxkYXRhYmFzZShLRUdHKSIpCgpnZXRfdW5pcHJvdCA8LSBmdW5jdGlvbih1cmwpIHsKICAjIHJlc2V0IHNlY3VyaXR5IGxldmVsLCBjYXVzZWQgYnkgYSBmYXVsdHkgU1NMIGNlcnRpZmljYXRlIG9uIHNlcnZlciBzaWRlLAogICMgc2VlIHRoaXMgdGhyZWFkOiBodHRwczovL2dpdGh1Yi5jb20vRW5zZW1ibC9lbnNlbWJsLXJlc3QvaXNzdWVzLzQyNwogIGh0dHJfY29uZmlnIDwtIGNvbmZpZyhzc2xfY2lwaGVyX2xpc3QgPSAiREVGQVVMVEBTRUNMRVZFTD0xIikKICByZXMgPC0gd2l0aF9jb25maWcoY29uZmlnID0gaHR0cl9jb25maWcsIEdFVCh1cmwpKQogIHNlcnZlcl9lcnJvciA9IHNpbXBsZUVycm9yKCIiKQogIGRmX3VuaXByb3QgPC0gdHJ5Q2F0Y2goCiAgICByZWFkX3Rzdihjb250ZW50KHJlcyksIGNvbF90eXBlcyA9IGNvbHMoKSksCiAgICBlcnJvciA9IGZ1bmN0aW9uKHNlcnZlcl9lcnJvcikgewogICAgICBtZXNzYWdlKCJVbmlwcm90IHNlcnZlciBub3QgYXZhaWxhYmxlLCBmYWxsaW5nIGJhY2sgb24gbG9jYWwgVW5pcHJvdCBEQiBjb3B5IikKICAgICAgcmVhZF90c3YoIi4uL2RhdGEvaW5wdXQvdW5pcHJvdF9zeW5lY2hvY3lzdGlzLnRzdiIsIGNvbF90eXBlcyA9IGNvbHMoKSkKICAgIH0KICApCn0KCmRmX3VuaXByb3QgPC0gZ2V0X3VuaXByb3QodW5pcHJvdF91cmwpICU+JQogIHJlbmFtZV93aXRoKHRvbG93ZXIpICU+JQogIHJlbmFtZShsb2N1cyA9IGBjcm9zcy1yZWZlcmVuY2UgKGtlZ2cpYCwgZ2VuZV9uYW1lID0gYGdlbmUgbmFtZXNgLAogICAgZ2VuZV9uYW1lX3Nob3J0ID0gYGdlbmUgbmFtZXMgIChwcmltYXJ5IClgLCBlY19udW1iZXIgPSBgZWMgbnVtYmVyYCwKICAgIHByb3RlaW4gPSBgcHJvdGVpbiBuYW1lc2AsIHVuaXByb3RfSUQgPSBlbnRyeQogICkgJT4lCiAgc2VwYXJhdGVfcm93cyhsb2N1cywgc2VwID0gIjtzeW46IikgJT4lCiAgbXV0YXRlKGxvY3VzID0gc3RyX3JlbW92ZV9hbGwobG9jdXMsICJzeW46fDsiKSkgJT4lCiAgZmlsdGVyKCFpcy5uYShsb2N1cykpCgpkZl9tYWluIDwtIGxlZnRfam9pbihkZl9tYWluLCBmaWx0ZXIoZGZfdW5pcHJvdCwgIWR1cGxpY2F0ZWQobG9jdXMpKSwKICBieSA9ICJsb2N1cyIpCmBgYAoKCiMjIE51bWJlciBvZiBzZ1JOQXMKCkVhY2ggZ2VuZSBpcyByZXByZXNlbnRlZCBieSB1cCB0byBmaXZlIHNnUk5Bcy4gV2UgY2FuIHRlc3QgaWYgYWxsIG9yIG9ubHkgc29tZSBvZiB0aGUgNSBzZ1JOQXMgYXJlICJiZWhhdmluZyIgaW4gdGhlIHNhbWUgd2F5IGluIHRoZSBzYW1lIGNvbmRpdGlvbnMsIG1vcmUgbWF0aGVtYXRpY2FsbHkgc3BlYWtpbmcgd2UgY2FuIGVzdGltYXRlIHRoZSBjb3JyZWxhdGlvbiBvZiBldmVyeSBzZ1JOQSB3aXRoIGFub3RoZXIuIEZpcnN0IGxldCdzIHN1bW1hcml6ZSBob3cgbWFueSBnZW5lcyBoYXZlIDUsIDQsIDMgc2dSTkFzIGFuZCBzbyBvbiBhc3NvY2lhdGVkIHdpdGggdGhlbS4KCmBgYHtyLCAsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQgPSAzLjV9CiMgTiB1bmlxdWUgc2dSTkFzIGluIGRhdGFzZXQKcGFzdGUwKCJOdW1iZXIgb2YgdW5pcXVlIHNnUk5BczogIiwgdW5pcXVlKGRmX21haW4kc2dSTkEpICU+JSBsZW5ndGgpCgojIE4gZ2VuZXMgd2l0aCAxLDIsMyw0IG9yIDUgc2dSTkFzCnBsb3Rfc2dSTkFzX3Blcl9nZW5lIDwtIGRmX21haW4gJT4lCiAgZ3JvdXBfYnkoc2dSTkFfdHlwZSwgc2dSTkFfdGFyZ2V0KSAlPiUKICBzdW1tYXJpemUobl9zZ1JOQXMgPSBsZW5ndGgodW5pcXVlKHNnUk5BX3Bvc2l0aW9uKSksIC5ncm91cHMgPSAiZHJvcF9sYXN0IikgJT4lCiAgY291bnQobl9zZ1JOQXMpICU+JSBmaWx0ZXIobl9zZ1JOQXMgPD0gNSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmFjdG9yKG5fc2dSTkFzLCA1OjEpLCB5ID0gbiwgbGFiZWwgPSBuKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3RleHQoc2l6ZSA9IDMsIG51ZGdlX3kgPSAyMDAsIGNvbG9yID0gZ3JleSgwLjUpKSArCiAgZmFjZXRfZ3JpZCh+IHNnUk5BX3R5cGUpICsKICBsYWJzKHggPSAic2dSTkFzIC8gdGFyZ2V0IiwgeSA9ICJ0YXJnZXRzIikgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygtNTAsIDM1MDApKSArCiAgY3VzdG9tX3RoZW1lKCkKCnByaW50KHBsb3Rfc2dSTkFzX3Blcl9nZW5lKQpgYGAKCiMjIEZpdG5lc3MgZGlzdHJpYnV0aW9uIG9mIGFsbCBjb25kaXRpb25zCgpCZWZvcmUgYmlvbG9naWNhbCBhbmFseXNpcyBjb250aW51ZXMsIHdlIG5lZWQgdG8gY2hlY2sgaWYgZml0bmVzcyAoYW5kIGxvZzIgRkMgZnJvbSB3aGljaCBpdCBpcyBjYWxjdWxhdGVkKSBpcyBlcXVhbGx5IGRpc3RyaWJ1dGVkLiBGb3IgZXhhbXBsZSwgc3RyaWN0bHkgZXNzZW50aWFsIGdlbmVzIGxpa2Ugcmlib3NvbWFsIGdlbmVzIHNob3VsZCBzaG93IHRoZSBzYW1lIGRlZ3JlZWUgb2YgZGVwbGV0aW9uIG92ZXIgdGltZSwgcmVnYXJkbGVzcyBvZiBjb25kaXRpb24uCgpXZSBjYW4gY29tcGFyZSBmaXRuZXNzIG92ZXIgYWxsIGNvbmRpdGlvbnMgdXNpbmcgYSBzY2F0dGVyIHBsb3QgbWF0cml4LiBXZSBjYW4gc2VlIHRoYXQgc29tZSBjb25kaXRpb25zIGFyZSB2ZXJ5IHNpbWlsYXIgdG8gZWFjaCBvdGhlciwgZm9yIGV4YW1wbGUgdGhlIGNvbmRpdGlvbnMgdHJlYXRlZCB3aXRoIGdsdWNvc2UgKGBMQywgTEwgK2dgLCBgTEMsIExMLCArRCwgK0dgLCBgSEMsIExMICtnYCkuIE90aGVycyBhcmUgbW9yZSBkaXNzaW1pbGFyIHRvIHRoZSByZXN0LCBmb3IgZXhhbXBsZSBgTEMsIElMYCBhbmQgYExDLCBMTCwgK0ZMYC4gVGhleSBhcmUgbW9yZSBhbGlrZSBlYWNoIG90aGVyLCBhbHRob3VnaCBgTEMsIExMLCArRkxgIHNob3VsZCBiZSBtb3JlIGNvbXBhcmFibGUgdG8gYExDLCBMTGAsIGhpbnRpbmcgYXQgZXhwZXJpbWVudGFsIGJpYXMuIEluIHRoaXMgY2FzZSBib3RoIG9mIHRoZXNlIGNvbmRpdGlvbnMgKGFuZCBgTEMsIExMLCArR2ApIHdlcmUgcHJlLWN1bHRpdmF0ZWQgaW4gbG93IGxpZ2h0IGluc3RlYWQgb2YgaGlnaCBsaWdodCwgYXMgb3Bwb3NlZCB0byB0aGUgcmVzdCBvZiB0aGUgc2FtcGxlcy4KCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KZGZfbWFpbiAlPiUgZmlsdGVyKHRpbWUgPT0gMCwgc2dSTkFfaW5kZXggPT0gMSkgJT4lCiAgc2VsZWN0KGxvY3VzLCBjb25kaXRpb24sIGZpdG5lc3MpICU+JQogIGZpbHRlcighaXMubmEobG9jdXMpKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gY29uZGl0aW9uLCB2YWx1ZXNfZnJvbSA9IGZpdG5lc3MpICU+JQogIHNlbGVjdCgtbG9jdXMpICU+JQogIGN1c3RvbV9zcGxvbShwY2ggPSAxOSwgY2V4ID0gMC4zLCBjb2wgPSBncmV5KDAuNCwgMC40KSwgcHNjYWxlcyA9IDApCmBgYAoKCkFub3RoZXIgd2F5IHRvIGxvb2sgYXQgdGhlIHJlc3VsdCBvZiB0aGUgbm9ybWFsaXphdGlvbiBpcyB0byBjb21wYXJlIHRoZSBnbG9iYWwgZGlzdHJpYnV0aW9uIG9mIGxvZzIgRkMgdmFsdWVzLCBhcyBhIGRlbnNpdHkgcGxvdC4KCmBgYHtyLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNiwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KGdncmlkZ2VzKQpkZl9tYWluICU+JSBmaWx0ZXIodGltZSA9PSAxMCkgJT4lCiAgc2VsZWN0KHNnUk5BLCBjb25kaXRpb24sIGxvZzJGb2xkQ2hhbmdlKSAlPiUKICBkaXN0aW5jdCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IGNvbmRpdGlvbiwgZ3JvdXAgPSBjb25kaXRpb24pKSArIAogIGdlb21fZGVuc2l0eV9yaWRnZXMoZmlsbCA9ICIjMDBBRkJCOTkiLCBjb2wgPSBncmV5KDAuNCkpICsKICBsaW1zKHggPSBjKC0yLCAxLjUpKSArCiAgY3VzdG9tX3RoZW1lKCkKYGBgCgojIEdlbmUgZml0bmVzcwoKU2dSTkEgZml0bmVzcyBzY29yZSB3YXMgY2FsY3VsYXRlZCBhcyB0aGUgQVVDIG9mIGxvZzJGQyByZWFkIG51bWJlciBvdmVyIGdlbmVyYXRpb24gdGltZSwgbm9ybWFsaXplZCBieSBnZW5lcmF0aW9uIHRpbWUuIEdlbmUgZml0bmVzcyBzY29yZSB3YXMgY2FsY3VsYXRlZCBhcyB0aGUgd2VpZ2h0ZWQgbWVhbiBvZiBpbmRpdmlkdWFsIHNnUk5BIGZpdG5lc3Mgc2NvcmVzLiBUaGUgd2VpZ2h0cyBhcmUgbWFkZSB1cCBvZiB0d28gZGlmZmVyZW50IGNvbXBvbmVudHMsCgogIDEuIGd1aWRlIFJOQSBjb3JyZWxhdGlvbgogIDIuIGd1aWRlIFJOQSBlZmZpY2llbmN5CgoKIyMgR3VpZGUgUk5BIGNvcnJlbGF0aW9uCgpBIGNvcnJlbGF0aW9uIHNjb3JlIHdhcyBjYWxjdWxhdGVkIGJ5IGNvbXB1dGluZyB0aGUgY29ycmVsYXRpb24gY29lZmZpY2llbnQgb2YgYWxsIHNnUk5BcyB0byBlYWNoIG90aGVyLiBUaGlzIHNjb3JlIGlzIHJvYnVzdGx5IHN1bW1hcml6ZWQgYnkgdGFraW5nIHRoZSBtZWRpYW4sIGFuZCByZXNjYWxpbmcgaXQgZnJvbSB0aGUgcmVzcGVjdGl2ZSBtaW5pbWEgYW5kIG1heGltYSBbLTEsIDFdIHRvIFswLCAxXS4gVGhpcyBzY29yZSBzZXJ2ZWQgYXMgYSB3ZWlnaHQgY29tcG9uZW50IGZvciBlYWNoIHNnUk5BIHRvIGNhbGN1bGF0ZSB0aGUgKGdsb2JhbCkgd2VpZ2h0ZWQgbWVhbiBvZiBsb2cyIEZDIGFuZCBmaXRuZXNzIG92ZXIgYWxsIHNnUk5Bcy4gVGhlIHNjb3JlIGhhcyB0aGUgY2hhcmFjdGVyaXN0aWMgdGhhdCBpdCBnaXZlcyBhIHdlaWdodCBvZiAxIGZvciBhbiBzZ1JOQSBwZXJmZWN0bHkgY29ycmVsYXRlZCB3aXRoIGFsbCBvdGhlciBzZ1JOQXMgb2YgdGhlIHNhbWUgZ2VuZSwgYW5kIGEgd2VpZ2h0IG9mIDAgZm9yIHNnUk5BcyBwZXJmZWN0bHkgYW50aS1jb3JyZWxhdGVkIHRvIHRoZSBvdGhlciBzZ1JOQXMuCgpGb3IgYSBtYXRyaXggb2YgJHggPSAxIC4uIG0kIHNnUk5BcyBhbmQgJHkgPSAxIC4uIG4kIG9ic2VydmF0aW9ucyAobWVhc3VyZW1lbnRzKSwgdGhlIGNvcnJlbGF0aW9uICRSJCBvZiBvbmUgc2dSTkEgdG8gYW5vdGhlciBpcyBjYWxjdWxhdGVkIHVzaW5nIFBlYXJzb24ncyBtZXRob2Q6CgokUl94PWNvcihbbG9nXzJGQ197eDEseTF9IC4uLiBsb2dfMkZDX3t4MSx5bn1dLCBbbG9nXzJGQ197eDIseTF9IC4uLiBsb2dfMkZDX3t4Mix5bn1dKSQKClRoZSBjb3JyZWxhdGlvbiB3ZWlnaHQgb2Ygb25lIHNnUk5BIGlzIHRoZW4gY2FsY3VsYXRlZCBhcyBtZWRpYW4gb2YgYWxsICRSJCByZXNjYWxlZCBiZXR3ZWVuIDAgYW5kIDEuCgokd194ID0gXGZyYWN7MSArIG1lZGlhbihSXzEsIFJfMiwgLi4uLCBSX20pfXsyfSQKClRoZSBmb2xsb3dpbmcgZXhhbXBsZSBzaG93cyB0aGUgY29ycmVsYXRpb24gbWF0cml4IGZvciB0aGUgNSBgcnBzMTBgIHNnUk5BcywgYW5kIHRoZWlyIHdlaWdodHMuIFRoZSBzZWxmIGNvcnJlbGF0aW9uIG9mIGVhY2ggc2dSTkEgKFIgPSAxKSBpcyByZW1vdmVkIHByaW9yIHRvIHdlaWdodCBkZXRlcm1pbmF0aW9uLgoKYGBge3IsIGZpZy53aWR0aCA9IDQsIGZpZy5oZWlnaHQgPSA0fQpjb3JfbWF0cml4IDwtIGRmX21haW4gJT4lIGZpbHRlcihzZ1JOQV90YXJnZXQgPT0gInJwczEwIikgJT4lIHVuZ3JvdXAgJT4lCiAgc2VsZWN0KHNnUk5BX2luZGV4LCBsb2cyRm9sZENoYW5nZSwgY29uZGl0aW9uLCB0aW1lKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gYygiY29uZGl0aW9uIiwgInRpbWUiKSwgdmFsdWVzX2Zyb20gPSBsb2cyRm9sZENoYW5nZSkgJT4lCiAgYXJyYW5nZShzZ1JOQV9pbmRleCkgJT4lIGNvbHVtbl90b19yb3duYW1lcygic2dSTkFfaW5kZXgiKSAlPiUKICBhcy5tYXRyaXggJT4lIHQgJT4lIGNvcihtZXRob2QgPSAicGVhcnNvbiIpCgp3ZWlnaHRzIDwtIGNvcl9tYXRyaXggJT4lIHJlcGxhY2UoLiwgLiA9PSAxLCBOQSkgJT4lCiAgYXBwbHkoMiwgZnVuY3Rpb24oeCkgbWVkaWFuKHgsIG5hLnJtID0gVFJVRSkpICU+JQogIHJlc2NhbGUoZnJvbSA9IGMoLTEsIDEpLCB0byA9IGMoMCwgMSkpCgojIHBsb3QgaGVhdG1hcApsYXR0aWNlOjpsZXZlbHBsb3QoY29yX21hdHJpeCAlPiUgcmVwbGFjZSguLCAuID09IDEsIE5BKSwKICBjb2wucmVnaW9ucyA9IGN1c3RvbV9yYW5nZSgyMCkpCgojIHByaW50IHdlaWdodHMKd2VpZ2h0cwpgYGAKCgojIyBHdWlkZSBSTkEgZWZmaWNpZW5jeQoKVGhlIGNvcnJlbGF0aW9uIG9mIGVhY2ggc2dSTkEgd2l0aCBlYWNoIG90aGVyIGlzIGEgImdsb2JhbCIgcGFyYW1ldGVyIGFzIGl0IGlzIGlkZW50aWNhbCBvdmVyIGFsbCBjb25kaXRpb25zLiBBIHNlY29uZCBnbG9iYWwgcGFyYW1ldGVyLCAqKnNnUk5BIGVmZmljaWVuY3kqKiwgd2FzIG9idGFpbmVkIHVzaW5nIGEgc2ltaWxhciBhcHByb2FjaC4gV2UgZXhwZWN0IHRoYXQgZml0bmVzcyBvZiBhbGwgc2dSTkFzIGZvciBvbmUgZ2VuZSBpcyBub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgYmVjYXVzZSBzZ1JOQXMgYXJlIG5vdCBpZGVhbCByZXBsaWNhdGUgbWVhc3VyZW1lbnRzLiBUaGV5IGFyZSBiaWFzZWQgYnkgcG9zaXRpb24gZWZmZWN0cyBhbmQgb2ZmLXRhcmdldCBiaW5kaW5nLCBzZWUgW1dhbmcgZXQgYWwuLCBOYXR1cmUgQ29tbXMsIDIwMThdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNDY3LTAxOC0wNDg5OS14KSBmb3IgYSB2ZXJ5IGluc2lnaHRmdWwgYW5kIGNvbXByZWhlbnNpdmUgYW5hbHlzaXMgb2YgdGhlIG51bWJlciBhbmQgcG9zaXRpb24gb2Ygc2dSTkFzIHJlcXVpcmVkIHRvIGVzdGltYXRlIGdlbmUgZml0bmVzcy4KCkhlcmUsIHNnUk5BIGVmZmljaWVuY3kgJEUkIHdhcyBjYWxjdWxhdGVkIGFzIHRoZSBtZWRpYW4gYWJzb2x1dGUgZml0bmVzcyAoQVVDIG9mIGxvZzJGQyBvdmVyIHRpbWUpIG9mIGFuIHNnUk5BICR4ID0gMSAuLiBtJCBvdmVyIGFsbCBvYnNlcnZhdGlvbnMgW2NvbmRpdGlvbnNdICR5ID0gMSAuLiBuJC4KCiRFX3g9bWVkaWFuKGFicyhmaXRuZXNzX3t4MSwgeTF9LCBmaXRuZXNzX3t4MSwgeTJ9LCAuLi4sIGZpdG5lc3Nfe3gxLCB5bn0pKSQKClRvIG5vcm1hbGl6ZSBiZXR3ZWVuIGFsbCBzZ1JOQXMsICRFJCBpcyByZXNjYWxlZCB0byBhIHJhbmdlIGJldHdlZW4gMCBhbmQgMS4KCiRFX3g9XGZyYWN7RV94fXttYXgoRV8xLCBFXzIsIC4uLiwgRV9tKX0kCgoKVGhpcyBpcyB0aGUgcmVzdWx0aW5nIHNnUk5BIGVmZmljaWVuY3kgZm9yIHRoZSBleGFtcGxlIGdlbmUgYWJvdmUsIGBycHMxMGAuCgpgYGB7cn0KZGZfbWFpbiAlPiUgZmlsdGVyKHNnUk5BX3RhcmdldCA9PSAicnBzMTAiKSAlPiUgdW5ncm91cCAlPiUKICBzZWxlY3Qoc2dSTkFfaW5kZXgsIHNnUk5BX2VmZmljaWVuY3kpICU+JSBkaXN0aW5jdCAlPiUgCiAgYXJyYW5nZShzZ1JOQV9pbmRleCkgJT4lIGRlZnJhbWUKYGBgCgoKIyMgUG9zaXRpb24gYmlhcyBvZiBzZ1JOQSByZXByZXNzaW9uCgpQbG90IHRoZSAqKndlaWdodCBvZiBlYWNoIHNnUk5BKiogdG8gc2VlIGlmIHRoZXJlIGlzIGEgZGVwZW5kZW5jeSBiZXR3ZWVuIGNvcnJlbGF0aW9uIGFuZCBzZ1JOQSBwb3NpdGlvbi4gVGhlcmUgaXMgbm8gc2lnbmlmaWNhbnQgdHJlbmQuCgpXZSBjYW4gYWxzbyBxdWFudGlmeSBob3cgbWFueSBnZW5lcyBoYXZlIHN0cm9uZ2x5IGNvcnJlbGF0ZWQgc2dSTkFzIGFuZCBob3cgbWFueSBoYXZlIG91dGxpZXJzLiBJbiBvcmRlciB0byBkbyB0aGlzLCB0aGUgbWVkaWFuIHdlaWdodCBvZiB0aGUgKHVwIHRvKSA1IHNnUk5BcyBwZXIgZ2VuZSBpcyBwbG90dGVkLiBHZW5lcmFsbHksIHRoZSBtZWRpYW4gd2VpZ2h0IHJhbmdlcyBiZXR3ZWVuIDAuNSBhbmQgMS4wLCBzaG93aW5nIG9uIGF2ZXJhZ2UgZ29vZCBjb3JyZWxhdGlvbi4KCmBgYHtyLCBmaWcud2lkdGggPSA2LCBmaWcuaGVpZ2h0ID0gM30KcGxvdF9zZ1JOQV9jb3JyZWxhdGlvbiA8LSBkZl9tYWluICU+JQogIHNlbGVjdChzZ1JOQV90YXJnZXQsIHNnUk5BX2luZGV4LCBzZ1JOQV9jb3JyZWxhdGlvbiwgc2dSTkFfdHlwZSkgJT4lCiAgZmlsdGVyKHNnUk5BX2luZGV4IDw9IDUsIHNnUk5BX3R5cGUgPT0gImdlbmUiKSAlPiUKICBkaXN0aW5jdCAlPiUKICAjIHBsb3QKICBnZ3Bsb3QoYWVzKHggPSBmYWN0b3Ioc2dSTkFfaW5kZXgpLCB5ID0gc2dSTkFfY29ycmVsYXRpb24pKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSAiIikgKwogIGxhYnMoeCA9ICJzZ1JOQSBwb3NpdGlvbiAocmVsYXRpdmUpIiwgeSA9ICJjb3JyZWxhdGlvbiIpICsKICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBjKHkgPSBtZWRpYW4oeCkrMC4wNywgCiAgICBsYWJlbCA9IHJvdW5kKG1lZGlhbih4KSwgMikpLCBnZW9tID0gInRleHQiLCBzaXplID0gMykgKwogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGMoeSA9IDEuMSwgCiAgICBsYWJlbCA9IGxlbmd0aCh4KSksIGdlb20gPSAidGV4dCIsIGNvbG9yID0gZ3JleSgwLjUpLCBzaXplID0gMykgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygtMC4xNSwgMS4xNSkpICsKICBjdXN0b21fdGhlbWUoKQoKcGxvdF9zZ1JOQV9jb3JyZWxhdGlvbl9oaXN0IDwtIGRmX21haW4gJT4lCiAgc2VsZWN0KHNnUk5BX3RhcmdldCwgc2dSTkFfaW5kZXgsIHNnUk5BX2NvcnJlbGF0aW9uLCBzZ1JOQV90eXBlKSAlPiUKICBmaWx0ZXIoc2dSTkFfaW5kZXggPD0gNSwgc2dSTkFfdHlwZSA9PSAiZ2VuZSIpICU+JQogIGRpc3RpbmN0ICU+JSBncm91cF9ieShzZ1JOQV90YXJnZXQpICU+JQogIHN1bW1hcml6ZSgKICAgIG1lZGlhbl9zZ1JOQV9jb3JyZWxhdGlvbiA9IG1lZGlhbihzZ1JOQV9jb3JyZWxhdGlvbiksCiAgICBtaW5fc2dSTkFfY29ycmVsYXRpb24gPSBtaW4oc2dSTkFfY29ycmVsYXRpb24pCiAgKSAlPiUKICAjIHBsb3QKICBnZ3Bsb3QoYWVzKHggPSBtZWRpYW5fc2dSTkFfY29ycmVsYXRpb24pKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDQwLCBmaWxsID0gY3VzdG9tX2NvbG9yc1sxXSwgYWxwaGEgPSAwLjcpICsKICBjdXN0b21fdGhlbWUoKQoKZ2dhcnJhbmdlKHBsb3Rfc2dSTkFfY29ycmVsYXRpb24sIHBsb3Rfc2dSTkFfY29ycmVsYXRpb25faGlzdCwgbmNvbCA9IDIpCmBgYAoKU2Vjb25kLCB0aGUgYmluZGluZyBwb3NpdGlvbiBvZiB0aGUgc2dSTkFzIGNvdWxkIGJlIGNvcnJlbGF0ZWQgdG8gdGhlIHN0cmVuZ3RoIG9mIHJlcHJlc3Npb24uIEluIG90aGVyIHdvcmRzIHNnUk5BcyBiaW5kaW5nIGNsb3NlciB0byB0aGUgcHJvbW90ZXIgY291bGQgaGF2ZSBzdHJvbmdlciBhYmlsaXR5IHRvIHJlcHJlc3MgYSBnZW5lLCBzZWUgRmlndXJlIDEgQiBpbiBbV2FuZyBldCBhbC4sIE5hdHVyZSBDb21tcywgMjAxOF0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zNDE0NjctMDE4LTA0ODk5LXgpLiBXZSBwbG90ICoqc2dSTkEgZWZmaWNpZW5jeSoqIGZvciBnZW5lcyBvbmx5LCBiZWNhdXNlIHRoZSBhYnNvbHV0ZSBtYWpvcml0eSBvZiB0aG9zZSBoYXMgNSBzZ1JOQXMuCgpgYGB7ciwgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodCA9IDN9CnBsb3Rfc2dSTkFfZWZmaWNpZW5jeSA8LSBkZl9tYWluICU+JQogIGZpbHRlcihzZ1JOQV9pbmRleCA8PSA1LCBzZ1JOQV90eXBlID09ICJnZW5lIikgJT4lCiAgc2VsZWN0KHNnUk5BX3RhcmdldCwgc2dSTkFfaW5kZXgsIHNnUk5BX2VmZmljaWVuY3kpICU+JSBkaXN0aW5jdCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBmYWN0b3Ioc2dSTkFfaW5kZXgpLCB5ID0gc2dSTkFfZWZmaWNpZW5jeSkpICsKICBnZW9tX2JveHBsb3Qobm90Y2ggPSBGQUxTRSwgb3V0bGllci5zaGFwZSA9ICIuIikgKwogIGxhYnMoeCA9ICJzZ1JOQSBwb3NpdGlvbiAocmVsYXRpdmUpIiwgeSA9ICJyZXByZXNzaW9uIGVmZmljaWVuY3kiKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKC0wLjE1LCAxLjE1KSkgKwogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGMoeSA9IG1lZGlhbih4KSswLjA3LCAKICAgIGxhYmVsID0gcm91bmQobWVkaWFuKHgpLCAyKSksIGdlb20gPSAidGV4dCIsIHNpemUgPSAzKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgYyh5ID0gMS4xLCAKICAgIGxhYmVsID0gbGVuZ3RoKHgpKSwgZ2VvbSA9ICJ0ZXh0IiwgY29sb3IgPSBncmV5KDAuNSksIHNpemUgPSAzKSArCiAgY3VzdG9tX3RoZW1lKCkKCgpwbG90X3NnUk5BX2VmZmljaWVuY3lfaGlzdCA8LSBkZl9tYWluICU+JQogIGZpbHRlcihzZ1JOQV9pbmRleCA8PSA1LCBzZ1JOQV90eXBlID09ICJnZW5lIikgJT4lCiAgc2VsZWN0KHNnUk5BX3RhcmdldCwgc2dSTkFfcG9zaXRpb24sIHNnUk5BX2VmZmljaWVuY3kpICU+JSBkaXN0aW5jdCAlPiUKICBncm91cF9ieShzZ1JOQV9wb3NpdGlvbikgJT4lCiAgc3VtbWFyaXplKHNnUk5BX2VmZmljaWVuY3kgPSBtZWRpYW4oc2dSTkFfZWZmaWNpZW5jeSksIG5fcG9zID0gbigpKSAlPiUKICBmaWx0ZXIobl9wb3MgPj0gMTApICU+JQogIGdncGxvdChhZXMoeCA9IHNnUk5BX3Bvc2l0aW9uLCB5ID0gc2dSTkFfZWZmaWNpZW5jeSkpICsKICBsYWJzKHggPSAic2dSTkEgcG9zaXRpb24gKG50KSIsIHkgPSAicmVwcmVzc2lvbiBlZmZpY2llbmN5IikgKwogIGdlb21fcG9pbnQoY29sID0gYWxwaGEoY3VzdG9tX2NvbG9yc1s1XSwgMC41KSkgKwogIGdlb21fc21vb3RoKCkgKwogIGN1c3RvbV90aGVtZSgpCgpnZ2FycmFuZ2UocGxvdF9zZ1JOQV9lZmZpY2llbmN5LCBwbG90X3NnUk5BX2VmZmljaWVuY3lfaGlzdCwgbmNvbCA9IDIpCmBgYAoKRXhwb3J0IGRyYWZ0ICoqRmlndXJlIDEqKiBmb3IgbWFudXNjcmlwdC4KCmBgYHtyLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNS41fQpwbG90X3NlbGVjdGVkX3NnUk5BcyA8LSBkZl9tYWluICU+JQogIGZpbHRlcigKICAgIGdyZXBsKCJjdHJsWzEtNV0kfHJwczEwJCIsIHNnUk5BX3RhcmdldCksIAogICAgY29uZGl0aW9uICVpbiUgYygiSEMsIEhMIiwgIkhDLCBMTCIsICJMQywgSUwiLCAiTEMsIExMIikpICU+JQogIG11dGF0ZSgKICAgIHNnUk5BX2luZGV4MiA9IGFzLm51bWVyaWMoc3RyX2V4dHJhY3Qoc2dSTkFfdGFyZ2V0LCAiWzEtOV0kIikpLAogICAgc2dSTkFfaW5kZXggPSBjYXNlX3doZW4oc2dSTkFfcG9zaXRpb24gPT0gMCB+IHNnUk5BX2luZGV4MiwgVFJVRSB+IHNnUk5BX2luZGV4KSwKICAgIHNnUk5BX3RhcmdldCA9IHN0cl9leHRyYWN0KHNnUk5BX3RhcmdldCwgIlthLXpBLVpdKiIpCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0aW1lLCB5ID0gbG9nMkZvbGRDaGFuZ2UsIGNvbG9yID0gZmFjdG9yKHNnUk5BX2luZGV4KSkpICsKICBnZW9tX2xpbmUoc2l6ZSA9IDEpICsgZ2VvbV9wb2ludChzaXplID0gMikgKwogIGZhY2V0X2dyaWQoc2dSTkFfdGFyZ2V0IH4gY29uZGl0aW9uKSArCiAgY3VzdG9tX3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IDApICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLTQuNSwgMi41KSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBjKDAsMiw0LDYsOCwxMCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX3JhbmdlKDUpKQoKc3ZnKGZpbGVuYW1lID0gIi4uL2ZpZ3VyZXMvZmlndXJlMS5zdmciLCB3aWR0aCA9IDcsIGhlaWdodCA9IDUuNSkKZ2dhcnJhbmdlKG5jb2wgPSAyLCBucm93ID0gMiwgd2lkdGhzID0gYygwLjYsIDAuNCksIGxhYmVscyA9IGMoIkEiLCAiQyIsICJCIiwgIkQiKSwgZm9udC5sYWJlbCA9IGxpc3RfZm9udHBhcnMsCiAgcGxvdF9zZ1JOQXNfcGVyX2dlbmUgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxMiwxMiwxMiwxMiksICJwb2ludHMiKSksCiAgcGxvdF9zZ1JOQV9lZmZpY2llbmN5ICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMjYsMTIsMTIsMTIpLCAicG9pbnRzIikpLAogIHBsb3Rfc2VsZWN0ZWRfc2dSTkFzICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsLTQsMTIsMTQpLCAicG9pbnRzIikpLAogIHBsb3Rfc2dSTkFfY29ycmVsYXRpb24gKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygyNiwxMiwxMiwxMiksICJwb2ludHMiKSkKKQpkZXYub2ZmKCkKYGBgCgpFeHBvcnQgKipzdXBwbGVtZW50YWwgZmlndXJlIHdpdGggYWxsIHJpYm9zb21hbCBnZW5lcyoqIChycHMqTk4qL3JwbCpOTiopLgoKYGBge3IsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSAxMH0KcGxvdF9zZ1JOQXNfcmlib3NvbWUgPC0gZGZfbWFpbiAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChzZ1JOQV90YXJnZXQsICJycFtzbF1bMC05XSokIikpICU+JQogIGZpbHRlcihjb25kaXRpb24gPT0gIkxDLCBMTCIpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBsb2cyRm9sZENoYW5nZSwgY29sb3IgPSBmYWN0b3Ioc2dSTkFfaW5kZXgpKSkgKwogIGdlb21fbGluZShzaXplID0gMSkgKyBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgZmFjZXRfd3JhcCh+IHNnUk5BX3RhcmdldCwgbmNvbCA9IDcpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX3JhbmdlKDUpKQoKcHJpbnQocGxvdF9zZ1JOQXNfcmlib3NvbWUpCmBgYAoKV2UgZ2VuZXJhdGUgYSByZWR1Y2VkIHRhYmxlIGZvY3VzaW5nIG9uIGdlbmUgZml0bmVzcyBpbnN0ZWFzIG9mIHNnUk5BIGZpdG5lc3MuClRoZSBkYXRhIHNldCBhbHJlYWR5IGNvbnRhaW5zIGd1aWRlIFJOQSBsZXZlbCBwLXZhbHVlcyBmcm9tIERFU2VxMiAoYHB2YWxgLCBgcGFkamApLCBhbmQgZ2VuZS1iYXNlZCBwLXZhbHVlcyBmcm9tIGZyb20gV2lsY294b24gUmFuayBTdW0gdGVzdCAoYHBfZml0bmVzc2AsIGBwX2ZpdG5lc3NfYWRqYCkuIFNpbmNlIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSB3YXMgdGVzdGVkIGZvciBtYW55IGdlbmVzIGluIHBhcmFsbGVsLCB0aGUgc2Vjb25kIGNvbHVtbnMgb2YgcC12YWx1ZXMgcmVwcmVzZW50cyBtdWx0aXBsZS1oeXBvdGhlc2lzIGNvcnJlY3RlZCBwLXZhbHVlcy4gVGhlIEJlbmphbWluaS1Ib2NoYmVyZyBtZXRob2Qgd2FzIHVzZWQgZm9yIHRoaXMgcHVycG9zZS4gVGhlIHBpcGVsaW5lIGFsc28gb3V0cHV0cyBhIHNjb3JlIHRoYXQgdGFrZXMgYm90aCBlZmZlY3Qgc2l6ZSBhbmQgcC12YWx1ZSBpbnRvIGFjY291bnQgKGBjb21iX3Njb3JlYCksIGFjY29yZGluZyB0byB0aGUgcHVibGljYXRpb24gZnJvbSBbV2FuZyBldCBhbC4sIE5hdCBDb21tLCAyMDE4XShodHRwOi8vZHguZG9pLm9yZy8xMC4xMDM4L3M0MTQ2Ny0wMTgtMDQ4OTkteCkuIFRoaXMgc2NvcmUgaXMgc2ltcGx5IHRoZSBhYnNvbHV0ZSBmaXRuZXNzIHNjb3JlIG11bHRpcGxpZWQgYnkgdGhlIG5lZ2F0aXZlIGxvZzEwIHAtdmFsdWUuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmRmX2dlbmUgPC0gZGZfbWFpbiAlPiUKICBzZWxlY3Qoc2dSTkFfdGFyZ2V0LCBzZ1JOQV90eXBlLCBsb2N1cywKICAgIGdlbmVfbmFtZSwgY29uZGl0aW9uLCAKICAgIGNhcmJvbiwgbGlnaHQsIHRyZWF0bWVudCwgdGltZSwKICAgIHdtZWFuX2xvZzJGb2xkQ2hhbmdlLCBzZF9sb2cyRm9sZENoYW5nZSwKICAgIHdtZWFuX2ZpdG5lc3MsIHNkX2ZpdG5lc3MsCiAgICBwX2ZpdG5lc3NfYWRqLCBjb21iX3Njb3JlCiAgKSAlPiUgZGlzdGluY3QKYGBgCgoKIyMgR2xvYmFsIGRpc3RyaWJ1dGlvbiBvZiBnZW5lIGZpdG5lc3MKCkdsb2JhbCBkaXN0cmlidXRpb24gb2Ygd2VpZ2h0ZWQgbWVhbiBmaXRuZXNzIGZvciBhbGwgZ2VuZXMuIEVmZmVjdCBvZiBuY1JOQSByZXByZXNzaW9uIHNlZW1zIHRvIGJlIG11Y2ggbG93ZXIgdGhhbiBlZmZlY3Qgb2YgZ2VuZSByZXByZXNzaW9uLgoKYGBge3IsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA1fQpwbG90X2FsbF9maXRuZXNzX2hpc3QgPC0gZGZfZ2VuZSAlPiUgZmlsdGVyKHRpbWUgPT0gMCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gd21lYW5fZml0bmVzcywgZmlsbCA9IHNnUk5BX3R5cGUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMCkgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygtNCwgNCksIHlsaW0gPSBjKDAsIDEwMDApKSArCiAgZmFjZXRfd3JhcCggfiBjb25kaXRpb24sIG5jb2wgPSA0KSArCiAgY3VzdG9tX3RoZW1lKCkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9jb2xvcnNbYygzOjQpXSkKCnByaW50KHBsb3RfYWxsX2ZpdG5lc3NfaGlzdCkKYGBgCgojIyBHZW5lIGZpdG5lc3MgdnMgc2lnbmlmaWNhbmNlCgpgYGB7ciwgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDMuNX0KcGxvdF9hbGxfZml0bmVzc192b2xjIDwtIGRmX2dlbmUgJT4lIGZpbHRlcih0aW1lID09IDAsCiAgICBjb25kaXRpb24gJWluJSBjKCJIQywgSEwiLCAiTEMsIExMIikpICU+JQogIGFycmFuZ2Uoc2dSTkFfdHlwZSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gd21lYW5fZml0bmVzcywgeSA9IC1sb2cxMChwX2ZpdG5lc3NfYWRqKSwgY29sID0gc2dSTkFfdHlwZSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBzaXplID0gMC41KSArCiAgZ2VvbV9saW5lKGRhdGEgPSBkYXRhLmZyYW1lKHggPSBjKHNlcSgtOCwgLTAuNSwgMC4xKSwgc2VxKDAuNSwgOCwgMC4xKSksCiAgICB5ID0gNC9jKHNlcSg4LCAwLjUsIC0wLjEpLCBzZXEoMC41LCA4LCAwLjEpKSksCiAgICBhZXMoeCA9IHgsIHkgPSB5LCBzaGFwZSA9IE5VTEwsIGNvbCA9IE5VTEwpLCBsdHkgPSAyKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC03LCA3KSwgeWxpbSA9IGMoMCwgMi41KSkgKwogIGN1c3RvbV90aGVtZShhc3BlY3QgPSAxLCBsZWdlbmQucG9zaXRpb24gPSAibGVmdCIsIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC40LCAiY20iKSkgKwogIGZhY2V0X3dyYXAofiBjb25kaXRpb24pICsKICBsYWJzKHggPSAiZml0bmVzcyIsIHkgPSBleHByZXNzaW9uKCItbG9nIlsxMF0qIiBwLXZhbHVlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX2NvbG9yc1szOjRdKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz1jKDEsIDE5KSkKCnByaW50KHBsb3RfYWxsX2ZpdG5lc3Nfdm9sYykKYGBgCgojIyBCZWhhdmlvciBvZiBjb250cm9sIHNnUk5BcwoKVGVuIHNnUk5BcyB3ZXJlIGluY2x1ZGVkIGluIHRoZSBsaWJyYXJ5IHRoYXQgaGF2ZSBubyBnZW5lLXNwZWNpZmljIHRhcmdldHMuIFRoZSBmb2xsb3dpbmcgcGxvdCBzaG93cyB0aGF0IHRoZXNlIG5lZ2F0aXZlIGNvbnRyb2xzIGRvIG5vdCBoYXZlIGFuIGVmZmVjdCBvbiBzdHJhaW4gZml0bmVzcywgZXhjZXB0IHByb2JhYmx5IDIgc2dSTkFzIGluIG9uZSBzcGVjaWZpYyBjb25kaXRpb24uCgpgYGB7ciwgZmlnLndpZHRoID0gNi41LCBmaWcuaGVpZ2h0ID0gNX0KcGxvdF9jb250cm9sc19zZ1JOQXMgPC0gZGZfbWFpbiAlPiUgZmlsdGVyKGdyZXBsKCJjdHJsIiwgc2dSTkFfdGFyZ2V0KSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IGxvZzJGb2xkQ2hhbmdlLCBjb2xvciA9IHNnUk5BX3RhcmdldCkpICsKICBnZW9tX2xpbmUoc2l6ZSA9IDEpICsgZ2VvbV9wb2ludChzaXplID0gMikgKyB5bGltKC01LCA1KSArCiAgZmFjZXRfd3JhcCh+IGNvbmRpdGlvbiwgbmNvbCA9IDQpICsKICBjdXN0b21fdGhlbWUoKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9yYW5nZSgxMCkpCgpwcmludChwbG90X2NvbnRyb2xzX3NnUk5BcykKc2F2ZV9wbG90KHBsb3RfY29udHJvbHNfc2dSTkFzLCB3aWR0aCA9IDYuNSwgaGVpZ2h0ID0gNS4wKQpgYGAKCiMgR2VuZSBlbnJpY2htZW50CgpUbyBwbG90IGdlbmUgZml0bmVzcyBmb3IgdGhlIGVuenltZXMgb2YgY2VudHJhbCBjYXJib24gbWV0YWJvbGlzbSwgd2UgbmVlZCBhIGNvbXBsZXRlIGxpc3Qgb2YgZW56eW1lcyBhbmQgdGhlIGdlbmVzIHRoYXQgdGhleSBhcmUgbWFwcGVkIHRvLiBUbyBsaXN0IHRoZSBkaWZmZXJlbnQgKipLRUdHIGRhdGFiYXNlcyoqIHRoYXQgY2FuIGJlIHF1ZXJpZWQsIHVzZSBgbGlzdERhdGFiYXNlcygpYC4gR2VuZS1wYXRod2F5IG1hcHBpbmdzIGFyZSBvYnRhaW5lZCBhbmQgbWVyZ2VkIHdpdGggcGF0aHdheSBuYW1lcyBhbmQgZ2VuZS9lbnp5bWUgbmFtZXMuCgoKYGBge3J9CiMgZ2V0IG1hcHBpbmcgb2YgcGF0aHdheXMgZm9yIGVhY2ggZ2VuZQpkZl9rZWdnIDwtIGtlZ2dMaW5rKCJwYXRod2F5IiwgInN5biIpICU+JQogIGVuZnJhbWUobmFtZSA9ICJsb2N1cyIsIHZhbHVlID0gImtlZ2dfcGF0aHdheV9pZCIpICU+JQogIAogICMgZ2V0IGxpc3Qgb2YgcGF0aHdheXMgd2l0aCBuYW1lL0lEIHBhaXJzCiAgbGVmdF9qb2luKGJ5ID0gImtlZ2dfcGF0aHdheV9pZCIsCiAgICBrZWdnTGlzdCgicGF0aHdheSIsICJzeW4iKSAlPiUKICAgIGVuZnJhbWUobmFtZSA9ICJrZWdnX3BhdGh3YXlfaWQiLCB2YWx1ZSA9ICJrZWdnX3BhdGh3YXkiKQogICkgJT4lCiAgCiAgIyBnZXQgbGlzdCBvZiBnZW5lL2VuenltZSBuYW1lcwogIGxlZnRfam9pbihieSA9ICJsb2N1cyIsCiAgICBrZWdnTGlzdCgic3luIikgJT4lCiAgICBlbmZyYW1lKG5hbWUgPSAibG9jdXMiLCB2YWx1ZSA9ICJrZWdnX2dlbmUiKSAlPiUKICAgIG11dGF0ZShrZWdnX2dlbmVfc2hvcnQgPSBzdHJfZXh0cmFjdChrZWdnX2dlbmUsICJeW2EtekEtWjAtOV0qOyIpICU+JSAKICAgICAgc3RyX3JlbW92ZSgiOyIpKQogICkgJT4lCiAgCiAgIyB0cmltIHVzZWxlc3MgcHJlZml4ZXMKICBtdXRhdGUoCiAgICBsb2N1cyA9IHN0cl9yZW1vdmUobG9jdXMsICJzeW46IiksCiAgICBrZWdnX3BhdGh3YXlfaWQgPSBzdHJfcmVtb3ZlKGtlZ2dfcGF0aHdheV9pZCwgInBhdGg6IiksCiAgICBrZWdnX3BhdGh3YXkgPSBzdHJfcmVtb3ZlKGtlZ2dfcGF0aHdheSwgIiAtIFN5bmVjaG9jeXN0aXMgc3AuIFBDQyA2ODAzIikKICApCgpoZWFkKGRmX2tlZ2cpCmBgYAoKIyMgRml0bmVzcyBwZXIgcGF0aHdheQoKU29tZXRpbWVzIGV2ZW4gc21hbGwgZWZmZWN0cyBpbiBmaXRuZXNzIGNhbiBiZSByZWxldmFudCBpZiBzZXZlcmFsIGdlbmVzIG9mIHRoZSBzYW1lIHBhdGh3YXkgKG9yIGlzby1lbnp5bWVzKSBhcmUgYWZmZWN0ZWQuIEEgc2ltcGxlIGZpdG5lc3MgdGhyZXNob2xkIHdpbGwgbm90IHJldmVhbCB0aG9zZSBjaGFuZ2VzLiBJbiBzdWNoIGNhc2VzIGEgbW9yZSBudWFuY2VkIGFwcHJvYWNoIGNhbiBiZSB0YWtlbiwgYSBnZW5lIHNldCBlbnJpY2htZW50IGFuYWx5c2lzIChHU0VBKS4gU2V2ZXJhbCBwYWNrYWdlcyBleGlzdCB0byB0ZXN0IGlmIGZ1bmN0aW9uYWxseSByZWxhdGVkIGdlbmVzIGFyZSBlbnJpY2hlZCwgZGVwbGV0ZWQsIG9yIGJvdGggYXQgdGhlIHNhbWUgdGltZSAvIHRoZSBzYW1lIGNvbmRpdGlvbnMuCgpCZWZvcmUgd2UgdGVzdCBmb3IgZW5yaWNobWVudCBvZiBhc3NvY2lhdGVkIHBhdGh3YXlzL0dPIHRlcm1zLCB3ZSBjYW4gaGF2ZSBhIGxvb2sgYXQgdGhlIGdlbmVyYWwgZGVwbGV0aW9uL2VucmljaG1lbnQgcGVyIEtFR0cgcGF0aHdheS4gVGhlIGZpdG5lc3MgZGlzdHJpYnV0aW9uIHBlciBwYXRod2F5IGNhbiBiZSB2aXN1YWxpemVkIHVzaW5nIGEgdmlvbGluLSBvciBzY2F0dGVyIHBsb3QuCgpgYGB7ciwgZmlnLndpZHRoID0gNS41LCBmaWcuaGVpZ2h0ID0gNS41fQpwbG90X21lZGlhbl9maXRuZXNzX2tlZ2cgPC0gZGZfZ2VuZSAlPiUgZmlsdGVyKHRpbWUgPT0gMCkgJT4lCiAgaW5uZXJfam9pbihkZl9rZWdnLCBieSA9ICJsb2N1cyIpICU+JQogIGdyb3VwX2J5KGtlZ2dfcGF0aHdheSwgY29uZGl0aW9uKSAlPiUKICBzdW1tYXJpemUoLmdyb3VwcyA9ICJkcm9wIiwKICAgIGZpdG5lc3MgPSBtZWRpYW4od21lYW5fZml0bmVzcyksCiAgICBuX2dlbmVzID0gbigpCiAgKSAlPiUgZmlsdGVyKG5fZ2VuZXMgPj0gMjApICU+JQogIG11dGF0ZShrZWdnX3BhdGh3YXkgPSBwYXN0ZTAoc3RyX3N1YihrZWdnX3BhdGh3YXksIDEsIDI1KSwgIi4uIikpICU+JQogIG11dGF0ZShrZWdnX3BhdGh3YXkgPSBmY3RfcmVvcmRlcihrZWdnX3BhdGh3YXksIGZpdG5lc3MsIC5kZXNjID0gVFJVRSkpICU+JQogIAogIGdncGxvdChhZXMoeCA9IGZpdG5lc3MsIHkgPSBrZWdnX3BhdGh3YXkpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOVUxMLCBjb2xvciA9IGdyZXkoMC41KSwgZmlsbCA9IGdyZXkoMC45KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY29uZGl0aW9uKSkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGx0eSA9IDIsIGNvbG9yID0gZ3JleSgwLjUpKSArCiAgbGFicyh4ID0gIm1lZGlhbiBmaXRuZXNzIiwgeSA9ICIiKSArCiAgY3VzdG9tX3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC4yNSwgMC4yNSksIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC40LCAiY20iKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yUmFtcFBhbGV0dGUoY3VzdG9tX2NvbG9yc1sxOjVdKSgxMSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sb3JSYW1wUGFsZXR0ZShjdXN0b21fY29sb3JzWzE6NV0pKDExKSkKCnByaW50KHBsb3RfbWVkaWFuX2ZpdG5lc3Nfa2VnZykKYGBgCgpFeHBvcnQgZHJhZnQgKipGaWd1cmUgMioqIGZvciBtYW51c2NyaXB0LgpXZSBhZGQgcGhvdG9zeXN0ZW0gSSBhbmQgSUkgZ2VuZXMgYXMgZXhhbXBsZXMgZm9yIGRpZmZlcmVudGlhbCBkZXBsZXRpb24uIEEgaGVhdG1hcC4KCmBgYHtyLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNH0KcGxvdF9zZ1JOQXNfcHMxIDwtIGRmX2dlbmUgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3Qoc2dSTkFfdGFyZ2V0LCAicHNhW0EtWl0qIiksIHRpbWUgPT0gMCkgJT4lCiAgbXV0YXRlKHdtZWFuX2ZpdG5lc3MgPSB3bWVhbl9maXRuZXNzICU+JSByZXBsYWNlKC4sIC4gPiA0LCA0KSAlPiUgcmVwbGFjZSguLCAuIDwgLTQsIC00KSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gY29uZGl0aW9uLCB5ID0gZmN0X3JldihzZ1JOQV90YXJnZXQpLCBmaWxsID0gd21lYW5fZml0bmVzcykpICsKICBnZW9tX3RpbGUoKSArIGN1c3RvbV90aGVtZSgpICsKICBsYWJzKHRpdGxlID0gIlBob3Rvc3lzdGVtIEkiLCB4ID0gIiIsIHkgPSAiIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdCA9IDEpKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IGMoY3VzdG9tX2NvbG9yc1sxXSwgZ3JleSgwLjkpLCBjdXN0b21fY29sb3JzWzJdKSwKICAgIGxpbWl0cyA9IGMoLTQsIDQpKQoKcGxvdF9zZ1JOQXNfcHMyIDwtIGRmX2dlbmUgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3Qoc2dSTkFfdGFyZ2V0LCAicHNiW0EtWl0qIiksIHRpbWUgPT0gMCkgJT4lCiAgbXV0YXRlKHdtZWFuX2ZpdG5lc3MgPSB3bWVhbl9maXRuZXNzICU+JSByZXBsYWNlKC4sIC4gPiA0LCA0KSAlPiUgcmVwbGFjZSguLCAuIDwgLTQsIC00KSkgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IHN0cl9yZXBsYWNlKHNnUk5BX3RhcmdldCwgInBzYjEzIiwgInBzYlciKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gY29uZGl0aW9uLCB5ID0gZmN0X3JldihzZ1JOQV90YXJnZXQpLCBmaWxsID0gd21lYW5fZml0bmVzcykpICsKICBnZW9tX3RpbGUoKSArIGN1c3RvbV90aGVtZSgpICsKICBsYWJzKHRpdGxlID0gIlBob3Rvc3lzdGVtIElJIiwgeCA9ICIiLCB5ID0gIiIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3QgPSAxKSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBjKGN1c3RvbV9jb2xvcnNbMV0sIGdyZXkoMC45KSwgY3VzdG9tX2NvbG9yc1syXSksCiAgICBsaW1pdHMgPSBjKC00LCA0KSkKCmdnYXJyYW5nZShuY29sID0gMiwgcGxvdF9zZ1JOQXNfcHMxLCBwbG90X3NnUk5Bc19wczIpCmBgYAoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA3fQpzdmcoZmlsZW5hbWUgPSAiLi4vZmlndXJlcy9maWd1cmUyLnN2ZyIsIHdpZHRoID0gOCwgaGVpZ2h0ID0gNykKZ2dhcnJhbmdlKG5jb2wgPSAyLCB3aWR0aHMgPSBjKDAuNjUsIDAuMzUpLAogIGdnYXJyYW5nZShucm93ID0gMiwgaGVpZ2h0cyA9ICBjKDAuMzQsIDAuNjYpLCBsYWJlbHMgPSBMRVRURVJTWzE6Ml0sIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogICAgcGxvdF9hbGxfZml0bmVzc192b2xjICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTQsLTgsMTQsNDApLCAicG9pbnRzIikpLAogICAgcGxvdF9tZWRpYW5fZml0bmVzc19rZWdnICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoNiwxMiwxMiwxMiksICJwb2ludHMiKSkpLAogIGdnYXJyYW5nZShucm93ID0gMiwgaGVpZ2h0cyA9ICBjKDAuNCwgMC42KSwgbGFiZWxzID0gTEVUVEVSU1szOjRdLCBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywKICAgIHBsb3Rfc2dSTkFzX3BzMSArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLDAsLTE0LDApLCAicG9pbnRzIikpLAogICAgcGxvdF9zZ1JOQXNfcHMyICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsMCwwLDApLCAicG9pbnRzIikpCiAgKQopCmRldi5vZmYoKQpgYGAKCiMjIEdlbmUgZW5yaWNobWVudCBhbmFseXNpcyAoS0VHRykKCldlIHVzZSB0aGUgZnVuY3Rpb25zIGBrZWdnYWAgZm9yIEtFR0cgZW5yaWNobWVudCBhbmFseXNpcyBhbmQgYGdvYW5hYCBmb3IgR08gdGVybSBlbnJpY2htZW50IGZyb20gdGhlIGBsaW1tYWAgcGFja2FnZS4gQm90aCBmdW5jdGlvbnMgdGVzdCBmb3Igb3ZlciBvciB1bmRlci1yZXByZXNlbnRhdGlvbiBvZiBnZW5lcyBhc3NvY2lhdGVkIHdpdGggY2VydGFpbiBwYXRod2F5cyBvciBHTyB0ZXJtcy4gVGhlIGZ1bmN0aW9ucyBkb24ndCB0YWtlIHRoZSBzdHJlbmd0aCBvZiBkaWZmZXJlbnRpYWwgZml0bmVzcyBpbnRvIGFjY291bnQgKERGOyB0aGUgZGVwbGV0aW9uL2VucmljaG1lbnQgb3ZlciB0aW1lKS4KCgpgYGB7cn0KZGZfa2VnZ19lbnJpY2htZW50IDwtIGxhcHBseSh1bmlxdWUoZGZfZ2VuZSRjb25kaXRpb24pLCBmdW5jdGlvbihjb25kKSB7CiAgZGZfZ2VuZSAlPiUgZmlsdGVyKAogIHNnUk5BX3R5cGUgPT0gImdlbmUiLCB0aW1lID09IDAsCiAgY29uZGl0aW9uID09IGNvbmQpICU+JQogIAogICMgZmlsdGVyIGZvciBkaWZmZXJlbnRpYWwgZml0bmVzcyAoREYpIGdlbmVzCiAgZmlsdGVyKCFiZXR3ZWVuKHdtZWFuX2ZpdG5lc3MsIC0yLjAsIDIuMCksICFpcy5uYShsb2N1cykpICU+JQogIAogICMgcGVyZm9ybSBLRUdHIGVucmljaG1lbnQKICBwdWxsKGxvY3VzKSAlPiUga2VnZ2Eoc3BlY2llcy5LRUdHID0gInN5biIpICU+JQogIG11dGF0ZShjb25kaXRpb24gPSBjb25kKQp9KSAlPiUgYmluZF9yb3dzCgpoZWFkKGRmX2tlZ2dfZW5yaWNobWVudCkKYGBgCgpOb3cgd2UgdmlzdWFsaXplIHRoZSBwYXRod2F5cyB0aGF0IGFyZSBtb3N0IGVucmljaGVkIGZvciBERiBnZW5lcy4gSXQgdHVybnMgb3V0IHRoYXQgcmlib3NvbWFsIHByb3RlaW5zIGFyZSBleHRyZW1lbHkgZGVwbGV0ZWQgYW5kIHRoZXJlZm9yZSBzY29yZSBoaWdoIG9uIHRoZSBuZWdhdGl2ZSBsb2cxMCBwLXZhbHVlIGZvciBwYXRod2F5IGVucmljaG1lbnQuCgpgYGB7ciwgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDUuNX0KZGZfa2VnZ19lbnJpY2htZW50ICU+JQogIHJlbmFtZShrZWdnX3BhdGh3YXkgPSBQYXRod2F5KSAlPiUKICBncm91cF9ieShrZWdnX3BhdGh3YXkpICU+JSBmaWx0ZXIoTiA+PSAyMCkgJT4lCiAgc2VsZWN0KGtlZ2dfcGF0aHdheSwgY29uZGl0aW9uLCBQLkRFKSAlPiUKICBtdXRhdGUobG9nMTBfcF92YWx1ZSA9IC1sb2cxMChQLkRFKSwgLmtlZXAgPSAidW51c2VkIikgJT4lCiAgbXV0YXRlKGtlZ2dfcGF0aHdheSA9IHBhc3RlMChzdHJfc3ViKGtlZ2dfcGF0aHdheSwgMSwgMjUpLCAiLi4iKSkgJT4lCiAgCiAgIyBtYWtlIGNvcnJlbGF0aW9uIHBsb3QKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gY29uZGl0aW9uLCB2YWx1ZXNfZnJvbSA9IGxvZzEwX3BfdmFsdWUpICU+JQogIGNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAia2VnZ19wYXRod2F5IikgJT4lIGFzLm1hdHJpeCAlPiUKICBjb3JycGxvdChpcy5jb3JyID0gRkFMU0UsIHRsLmNvbCA9IGdyZXkoMC41KSwgdGwuY2V4ID0gMC44LAogICAgY29sID0gY29sb3JSYW1wUGFsZXR0ZShjdXN0b21fY29sb3JzW2MoMSw1LDIpXSkoMTApLCBjb2wubGltID0gYygwLCAyMCkpCmBgYAoKCiMgVW5zdXBlcnZpc2VkIGNsdXN0ZXJpbmcgb2YgZ2VuZXMKCiMjIENsdXN0ZXIgZ2VuZXMgYnkgc2ltaWxhcml0eQoKV2UgY2FuIGRldmlzZSBhIGdlbmVyYWxpemVkIGB0aWR5dmVyc2VgIGZyaWVuZGx5IGZ1bmN0aW9uIHRvIGNsdXN0ZXIgYSBuYW1lIHZhcmlhYmxlIGJ5IGEgdmFsdWUsIGdyb3VwZWQgYnkgb25lIG9yIG1vcmUgZ3JvdXBpbmcgdmFyaWFibGVzLiBGb3IgZXhhbXBsZSwgY2x1c3RlciBnZW5lcyAobmFtZSkgYnkgZml0bmVzcyAodmFsdWUpIG92ZXIgc2V2ZXJhbCBjb25kaXRpb25zIChncm91cHMpLiBUaGUgb3V0cHV0IGlzIGEgZmFjdG9yIHdpdGggcmUtb3JkZXJlZCBsZXZlbHMuCgpgYGB7cn0KZmN0X2NsdXN0ZXIgPC0gZnVuY3Rpb24odmFyaWFibGUsIGdyb3VwLCB2YWx1ZSwgbWV0aG9kID0gIndhcmQuRDIiKSB7CiAgZGYgPC0gdGliYmxlKHZhcmlhYmxlID0gdmFyaWFibGUsIGdyb3VwID0gZ3JvdXAsIHZhbHVlID0gdmFsdWUpCiAgZGYgPC0gcGl2b3Rfd2lkZXIoZGYsIG5hbWVzX2Zyb20gPSBncm91cCwgdmFsdWVzX2Zyb20gPSB2YWx1ZSkKICBtYXQgPC0gYXMubWF0cml4KGNvbHVtbl90b19yb3duYW1lcyhkZiwgdmFyID0gInZhcmlhYmxlIikpCiAgY2wgPC0gaGNsdXN0KGRpc3QobWF0KSwgbWV0aG9kID0gbWV0aG9kKQogIG9yZCA8LSBvcmRlci5kZW5kcm9ncmFtKGFzLmRlbmRyb2dyYW0oY2wpKQogIGZhY3Rvcih2YXJpYWJsZSwgdW5pcXVlKHZhcmlhYmxlKVtvcmRdKQp9CmBgYAoKSGVhdCBtYXAgb2YgZml0bmVzcyBmb3IgKmFsbCBnZW5lcyBhbmQgYWxsIGNvbmRpdGlvbnMqLgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSAyLjJ9CnBsb3RfaGVhdG1hcF9hbGwgPC0gZGZfZ2VuZSAlPiUgZmlsdGVyKHRpbWUgPT0gMCwgIWlzLm5hKGxvY3VzKSkgJT4lCiAgbXV0YXRlKGxvY3VzID0gZmN0X2NsdXN0ZXIobG9jdXMsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcykpICU+JQogIG11dGF0ZSh3bWVhbl9maXRuZXNzID0gd21lYW5fZml0bmVzcyAlPiUgcmVwbGFjZSguLCAuID4gNCwgNCkgJT4lIHJlcGxhY2UoLiwgLiA8IC00LCAtNCkpICU+JQogIGdncGxvdChhZXMoeCA9IGxvY3VzLCB5ID0gY29uZGl0aW9uLCBmaWxsID0gd21lYW5fZml0bmVzcykpICsKICBnZW9tX3RpbGUoKSArIGN1c3RvbV90aGVtZShsZWdlbmQucG9zID0gInJpZ2h0IikgKwogIGxhYnMoeCA9IHBhc3RlMCgiZ2VuZXMgKCIsIGxlbmd0aCh1bmlxdWUoZGZfZ2VuZSRsb2N1cykpLCIpIiksIHkgPSAiIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzID0gYyhjdXN0b21fY29sb3JzWzFdLCBncmV5KDAuOSksIGN1c3RvbV9jb2xvcnNbMl0pLAogICAgbGltaXRzID0gYygtNCwgNCkpCgpwcmludChwbG90X2hlYXRtYXBfYWxsKQpgYGAKCgpOb3cgd2UgY2FuIHBsb3QgX2FsbF8gZ2VuZXMsIGEgc3Vic2V0IHdpdGggX29ubHkgc2lnbmlmaWNhbnQgZ2VuZXNfLCBhbmQgYSBkZW5kcm9ncmFtIGZvciBjbHVzdGVyaW5nLiBUaGUgcmVzdWx0IGlzIGhhcmQgdG8gaW50ZXJwcmV0LiBXaXRoIHNvbWUgZXhjZXB0aW9ucywgbW9zdCBnZW5lcyBhcmUgZ3JvdXBlZCBpbiBicm9hZCB1bnNwZWNpZmljIGNsdXN0ZXJzIHRoYXQgZG8gbm90IHJldmVhbCBjbGVhciByZWxhdGlvbnNoaXBzIGJldHdlZW4gdHJlYXRtZW50IHZhcmlhYmxlcyBhbmQgZml0bmVzcyBvdXRjb21lLgoKCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNH0KIyBwcmVwYXJlIG5ldyBkZiBhbmQgcGxvdCBoZWF0bWFwCmRmX2hlYXRtYXAgPC0gZGZfZ2VuZSAlPiUgZmlsdGVyKHRpbWUgPT0gMCwgIWlzLm5hKGxvY3VzKSkgJT4lCiAgZ3JvdXBfYnkobG9jdXMpICU+JQogIGZpbHRlcihhbnkoIWJldHdlZW4od21lYW5fZml0bmVzcywgLTQsIDQpICYgcF9maXRuZXNzX2FkaiA8IDAuMDEpKSAlPiUgdW5ncm91cCAlPiUKICBtdXRhdGUobG9jdXMgPSBmY3RfY2x1c3Rlcihsb2N1cywgY29uZGl0aW9uLCB3bWVhbl9maXRuZXNzKSkgJT4lCiAgbXV0YXRlKHdtZWFuX2ZpdG5lc3MgPSB3bWVhbl9maXRuZXNzICU+JSByZXBsYWNlKC4sIC4gPiA4LCA4KSAlPiUgcmVwbGFjZSguLCAuIDwgLTgsIC04KSkKCnBsb3RfaGVhdG1hcF9zaWcgPC0gZGZfaGVhdG1hcCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsb2N1cywgeSA9IGNvbmRpdGlvbiwgZmlsbCA9IHdtZWFuX2ZpdG5lc3MpKSArCiAgZ2VvbV90aWxlKCkgKyBjdXN0b21fdGhlbWUobGVnZW5kLnBvcyA9ICJyaWdodCIpICsKICBsYWJzKHggPSBwYXN0ZTAoImdlbmVzICgiLCBsZW5ndGgodW5pcXVlKGRmX2hlYXRtYXAkbG9jdXMpKSwiKSIpLCB5ID0gIiIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IGMoY3VzdG9tX2NvbG9yc1sxXSwgZ3JleSgwLjkpLCBjdXN0b21fY29sb3JzWzJdKSwKICAgIGxpbWl0cyA9IGMoLTgsIDgpKQoKIyBwcmVwYXJlIGRpc3Qgb2JqZWN0IGZvciBjbHVzdGVyaW5nIGFuZCBwbG90IGRlbmQKZGlzdF9oZWF0bWFwIDwtIGRmX2hlYXRtYXAgJT4lIHNlbGVjdChsb2N1cywgY29uZGl0aW9uLCB3bWVhbl9maXRuZXNzKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gY29uZGl0aW9uLCB2YWx1ZXNfZnJvbSA9IHdtZWFuX2ZpdG5lc3MpICU+JQogIGNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAibG9jdXMiKSAlPiUgYXMubWF0cml4ICU+JQogIGRpc3QKCnBsb3RfY2x1c3Rlcl9kZW5kIDwtIGRpc3RfaGVhdG1hcCAlPiUKICBoY2x1c3QobWV0aG9kID0gIndhcmQuRDIiKSAlPiUgYXMuZGVuZHJvZ3JhbSAlPiUKICBzZXQoImJyYW5jaGVzX2tfY29sIiwgY3VzdG9tX2NvbG9yc1sxOjVdLCBrID0gNSkgJT4lCiAgc2V0KCJicmFuY2hlc19sd2QiLCAwLjUpICU+JQogIGFzLmdnZGVuZCAlPiUKICBnZ3Bsb3QobGFiZWxzID0gRkFMU0UpCgojIGFycmFuZ2UgYm90aCBvbiBzYW1lIHBsb3QKZ2dhcnJhbmdlKG5yb3cgPSAyLCBoZWlnaHRzID0gIGMoMC41LCAwLjUpLAogIHBsb3RfY2x1c3Rlcl9kZW5kICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMC4xLCAwLjA5LCAtMC4xNSwgMC4xMzYpLCJucGMiKSksCiAgcGxvdF9oZWF0bWFwX3NpZwopCmBgYAoKIyMgR2VuZSBzaW1pbGFyaXR5IGJ5IGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBtZXRob2RzCgpXZSB1c2UgdHdvIGRpZmZlcmVudCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gbWV0aG9kcywgKipuTURTKiogYW5kICoqdC1TTkUqKi4gV2UgY2FuIGNoZWNrIGlmIHRoZXNlIG1ldGhvZHMgcmVwcm9kdWNlIHRoZSBjbHVzdGVyaW5nIGZvciB0aGUgc2lnbmlmaWNhbnRseSByZWd1bGF0ZWQgZ2VuZXMgcHJvZHVjZWQgd2l0aCBgaGNsdXN0YC4gQW5hbHlzaXMgc2hvd3MgdGhhdCB0aGUgc21hbGwgY2x1c3RlcnMgYXJlIG1vcmUgc3Ryb25nbHkgc2VwYXJhdGVkIGZyb20gdGhlIHJlc3QuCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDR9CiMgc2V0IGEgc2VlZCB0byBvYnRhaW4gc2FtZSBwYXR0ZXJuIGZvciBzdG9jaGFzdGljIG1ldGhvZHMKc2V0LnNlZWQoMzIxKQoKIyBydW4gbk1EUyBhbmFseXNpcwpOTURTIDwtICBkaXN0X2hlYXRtYXAgJT4lIG1ldGFNRFMKZGZfbm1kcyA8LSBOTURTJHBvaW50cyAlPiUgYXNfdGliYmxlKHJvd25hbWVzID0gImxvY3VzIikgJT4lCiAgbGVmdF9qb2luKGVuZnJhbWUobmFtZSA9ICJsb2N1cyIsIHZhbHVlID0gImNsdXN0ZXIiLAogICAgY3V0cmVlb3JkKGhjbHVzdChkaXN0X2hlYXRtYXAsIG1ldGhvZCA9ICJ3YXJkLkQyIiksIGsgPSA1KSkpCgojIHJ1biB0LVNORSBhbmFseXNpcwpTTkUgPC0gZGlzdF9oZWF0bWFwICU+JSB0c25lKG1heF9pdGVyID0gNTAwLCBwZXJwbGV4aXR5ID0gOCkKZGZfdHNuZSA8LSBTTkUgJT4lIHNldE5hbWVzKGMoIngiLCAieSIpKSAlPiUgYXNfdGliYmxlICU+JQogIG11dGF0ZShsb2N1cyA9IHVuaXF1ZShkZl9oZWF0bWFwJGxvY3VzKSkgJT4lCiAgbGVmdF9qb2luKGVuZnJhbWUobmFtZSA9ICJsb2N1cyIsIHZhbHVlID0gImNsdXN0ZXIiLAogICAgY3V0cmVlb3JkKGhjbHVzdChkaXN0X2hlYXRtYXAsIG1ldGhvZCA9ICJ3YXJkLkQyIiksIGsgPSA1KSkpCgpwbG90X25tZHMgPC0gZGZfbm1kcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBNRFMxLCB5ID0gTURTMiwgY29sb3IgPSBmYWN0b3IoY2x1c3RlcikpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMikgKyBsYWJzKHRpdGxlID0gIm5NRFMiKSArCiAgY3VzdG9tX3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44NSwgMC43OCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX2NvbG9ycykKCnBsb3RfdHNuZSA8LSBkZl90c25lICU+JQogIGdncGxvdChhZXMoeCA9IFYxLCB5ID0gVjIsIGNvbG9yID0gZmFjdG9yKGNsdXN0ZXIpKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsgbGFicyh0aXRsZSA9ICJ0LVNORSIpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LCAwLjc4KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjdXN0b21fY29sb3JzKQoKZ2dhcnJhbmdlKG5jb2wgPSAyLCBwbG90X25tZHMsIHBsb3RfdHNuZSkKYGBgCgojIyBGaXQgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzCgpXZSBjYW4gZmluZCBjbHVzdGVycyBvZiBnZW5lcyB3aXRoIHNpbWlsYXIgZml0bmVzcywgYnV0IGl0IGlzIGFsc28gaW1wb3J0YW50IHRvIGlkZW50aWZ5IF93aHlfIHRoZXkgY2x1c3RlciB0b2dldGhlci4gSW4gb3JkZXIgdG8gZmluZCBvdXQgX3doaWNoIHZhcmlhYmxlc18gZGV0ZXJtaW5lIHRoZSBmaXRuZXNzIG91dGNvbWUgb2YgYSBnZW5lLCB3ZSBjYW4gcGVyZm9ybSAqKm11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uKiouIEVhY2ggZ2VuZSBuZWVkcyB0byBoYXZlIGZpdG5lc3Mgb3V0Y29tZXMgYW5ub3RhdGVkIHdpdGggdGhlIGRpZmZlcmVudCAobWl4ZWQpIHZhcmlhYmxlcyBgY2FyYm9uYCwgYGxpZ2h0YCwgYHRyZWF0bWVudGAuIFRoZSBsYXR0ZXIgY2FuIGJlIHN1YmRpdmlkZWQgaW4gaW5kaXZpZHVhbCB0cmVhdG1lbnQgY29sdW1ucyBnbHVjb3NlLCBEQ01VLCBmbHVjdHVhdGluZyBsaWdodCwgYW5kIHNvIG9uLiBNdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiBmaXRzIGEgbGluZWFyIG1vZGVsIG9mIHRoZSBmb2xsb3dpbmcgZm9ybSB0byB0aGUgZGF0YToKCmByZXNwb25zZSB+IGludGVyY2VwdCArIHByZWRpY3RvciBBIHggc2xvcGUgQSArIHByZWRpY3RvciBCIHggc2xvcGUgQiB4IC4uLmAKCkhlcmUsIGBmaXRuZXNzYCBpcyB0aGUgcmVzcG9uc2UgdmFyaWFibGUsIHRoZSBkaWZmZXJlbnQgY29uZGl0aW9ucyBhcmUgdGhlIHByZWRpY3RvcnMuIEl0IGlzIGltcG9ydGFudCB0byBjb252ZXJ0IHRoZSBjYXRlZ29yaWNhbCBwcmVkaWN0b3JzIGludG8gKG51bWVyaWNhbCkgZHVtbXkgdmFyaWFibGVzLiBUaGVuIGZvciBlYWNoIGluZGl2aWR1YWwgZ2VuZSwgbXVsdGlwbGUgbGluZWFyIG1vZGVscyBhcmUgZml0dGVkIGFuZCB0aGUgcG93ZXIgb2YgZWFjaCBwcmVkaWN0b3IgdmFyaWFibGUgdG8gcHJlZGljdCB0aGUgcmVzcG9uc2UgaXMgZXh0cmFjdGVkLgoKYGBge3J9CiMgZml4ZWQgbW9kZWwgd2l0aCA2IHByZWRpY3RvciB2YXJpYWJsZXMgLS0gZHluYW1pYyBsYXlvdXQgd291bGQgCiMgYmUgYmV0dGVyIGluIGZ1dHVyZQpmaXRfbGlucmVnIDwtIGZ1bmN0aW9uKHksIHgxLCB4MiwgeDMsIHg0LCB4NSwgeDYpewogIGZpdCA8LSBsbSh5IH4geDEgKyB4MiArIHgzICsgeDQgKyB4NSArIHg2KQogIGMoY29lZmZpY2llbnRzKGZpdCksIHN1bW1hcnkoZml0KSRjb2VmZmljaWVudHNbLCA0XSwKICAgIHN1bW1hcnkoZml0KSRyLnNxdWFyZWQpCn0KCiMgcmVjb2RlIGNhdGVnb3JpY2FsIHRvIG51bWVyaWNhbCAoZHVtbXkpIHZhcmlhYmxlcwpkZl9saW5yZWcgPC0gZGZfZ2VuZSAlPiUKICBmaWx0ZXIoIWlzLm5hKGxvY3VzKSkgJT4lCiAgc2VsZWN0KGxvY3VzLCBjYXJib24sIGxpZ2h0LCB0cmVhdG1lbnQsIHdtZWFuX2ZpdG5lc3MpICU+JSBkaXN0aW5jdCAlPiUKICBtdXRhdGUoCiAgICBjYXJib24gPSByZWNvZGUoY2FyYm9uLCBgSENgID0gMSwgYExDYCA9IDApLAogICAgbGlnaHQgPSByZWNvZGUobGlnaHQsIGBMTGAgPSAwLCBgSUxgID0gMC41LCBgSExgID0gMSkpICU+JQogIG11dGF0ZShkdW1teSA9IDEsIHRyZWF0bWVudCA9IHJlcGxhY2UodHJlYXRtZW50LCB0cmVhdG1lbnQgPT0gIiIsICItIikpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB0cmVhdG1lbnQsIHZhbHVlc19mcm9tID0gZHVtbXksIHZhbHVlc19maWxsID0gMCkgJT4lCiAgbXV0YXRlKGArR2AgPSBgK0dgICsgYCtELCArR2ApICU+JSByZW5hbWUoYCtEYCA9IGArRCwgK0dgKSAlPiUgc2VsZWN0KC1gLWApICU+JQogICMgZml0IG1vZGVsCiAgZ3JvdXBfYnkobG9jdXMpICU+JQogIHN1bW1hcml6ZShjb2VmZmljaWVudCA9IGZpdF9saW5yZWcod21lYW5fZml0bmVzcywgY2FyYm9uLCBsaWdodCwgYC1OYCwgYCtGTGAsIGArR2AsIGArRGApLAogICAgLmdyb3VwcyA9ICJrZWVwIikgJT4lCiAgbXV0YXRlKHRyZWF0bWVudCA9IGMocmVwKGMoImludGVyY2VwdCIsICJjYXJib24iLCAibGlnaHQiLCAiLU4iLCAiK0ZMIiwgIitHIiwgIitEIiksIDIpICU+JSAKICAgIHBhc3RlMChyZXAoYygiIiwgInB2YWxfIiksIGVhY2ggPSA3KSwgLiksICJyX3NxdWFyZWQiKSkKYGBgCgpOb3cgd2UgY2FuIG92ZXJsYXkgdGhlIGluZm9ybWF0aW9uIG9mIHRoZSBiZXN0IHByZWRpY3RvciB2YXJpYWJsZSBvbiB0aGUgY2x1c3RlciBtYXAgcHJvZHVjZWQgYnkgdFNORSwgZm9yIGV4YW1wbGUsIGFuZCB0aGlzIHdheSBpZGVudGlmeSBncm91cHMgb2YgZ2VuZXMgcmVndWxhdGVkIGluIGEgc2ltaWxhciBkZWdyZWUsIGJ5IHNpbWlsYXIgdmFyaWFibGVzLgoKYGBge3IsIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSAxMn0KcGxvdF90c25lX2xpbnJlZyA8LSBkZl90c25lICU+JQogIGlubmVyX2pvaW4oZGZfbGlucmVnLCBieSA9ICJsb2N1cyIpICU+JQogIGxlZnRfam9pbihzZWxlY3QoZGZfZ2VuZSwgbG9jdXMsIHNnUk5BX3RhcmdldCkgJT4lIGRpc3RpbmN0LCBieSA9ICJsb2N1cyIpICU+JQogIGZpbHRlcighc3RyX2RldGVjdCh0cmVhdG1lbnQsICJpbnRlcmNlcHR8cHZhbHxyX3NxdWFyZWQiKSkgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IGlmX2Vsc2UoYWJzKGNvZWZmaWNpZW50KSA+IDIsIHNnUk5BX3RhcmdldCwgIiIpKSAlPiUKICBtdXRhdGUocG9pbnRfc2l6ZSA9IGFicyhjb2VmZmljaWVudCksCiAgICBjb2VmZmljaWVudCA9IGNvZWZmaWNpZW50ICU+JSByZXBsYWNlKC4sIC4gPiA1LCA1KSAlPiUgcmVwbGFjZSguLCAuIDwgLTUsIC01KSkgJT4lCiAgCiAgZ2dwbG90KGFlcyh4ID0gVjEsIHkgPSBWMiwgc2l6ZSA9IHBvaW50X3NpemUsCiAgICBjb2xvciA9IGNvZWZmaWNpZW50LCBsYWJlbCA9IHNnUk5BX3RhcmdldCkpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnModGl0bGUgPSAidC1TTkUgY2x1c3RlcmluZyBvZiBnZW5lcyB3aXRoIGFic29sdXRlIGZpdG5lc3MgPiA0IGFuZCBwLXZhbHVlIDwgMC4wMS4iLCAKICAgIHN1YnRpdGxlID0gcGFzdGUwKCJkb3QgY29sb3Ivc2l6ZSBlbmNvZGVzIGVmZmVjdCBvZiB2YXJpYWJsZSBmcm9tIE1MUiwgbiA9ICIsIG5yb3coZGZfdHNuZSkpKSArCiAgY3VzdG9tX3RoZW1lKGFzcGVjdCA9IDEpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudG4obGltaXRzID0gYygtNSwgNSksCiAgICBjb2xvdXJzID0gYyhjdXN0b21fY29sb3JzWzFdLCBncmV5KDAuNiwgMC44KSwgY3VzdG9tX2NvbG9yc1syXSkpICsKICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDEsIDYpKSArCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzLCBtYXgub3ZlcmxhcHMgPSA1MCkgKwogIGZhY2V0X3dyYXAoIH4gdHJlYXRtZW50LCBuY29sID0gMikKCnByaW50KHBsb3RfdHNuZV9saW5yZWcpCmBgYAoKVGhpcyBzdHJhdGVneSByZXZlYWxzIGEgbGlzdCBvZiBpbnRlcmVzdGluZyBjb25kaXRpb24tc3BlY2lmaWMgZ2VuZXM6CgotIEZsdWN0dWF0aW5nIGxpZ2h0OgogIC0gYHNsbDAyMTdgIC0gUHV0YXRpdmUgZGlmbGF2aW4gZmxhdm9wcm90ZWluIEEyIChkZmEyIC8gRmx2NCkKICAtIGBzbGwwMjE4YCAtIFB1dGF0aXZlIGRpZmxhdmluIGZsYXZvcHJvdGVpbiBhc3NvY2lhdGVkIHByb3RlaW4KICAtIFB1dGF0aXZlIGRpZmxhdmluIGZsYXZvcHJvdGVpbnMgYHNsbDE1MjFgIChGbHYxKSBhbmQgYHNsbDA1NTBgIChGbHYzKSBzaG93IGludmVyc2UgYnV0IEZMKyBzcGVjaWZpYwogICAgZml0bmVzcyBwYXR0ZXJuIGJ1dCB3ZXJlIG5vdCBpbmNsdWRlZCBpbiBmaWd1cmUgYmVjYXVzZSBvZiBsb3dlciBzaWduaWZpY2FuY2UvRkMKLSBNaXhvdHJvcGh5OgogIC0gYHNsbDA1OTNgIC0gZ2xrLCBnbHVjb2tpbmFzZSwgY2F0YWx5emVzIFAteWxhdGlvbiBvZiBHbGMgdG8gRzZQCiAgLSBgc2xsMTUzM2AgLSBwaWxULCBmaW1icmlhIGFzc2VtYmx5LCBtb2JpbGl0eSwgR2xjIHRyYW5zcG9ydCBvciBzZW5zaW5nPwogIC0gYHNzbDMzNjRgIC0gQ1AxMiBzbWFsbCBwcm90ZWluLCBzdHJvbmdseSBpbnRlcmFjdHMgd2l0aCBSYmNYLCBSYmNSLCBQcmsuIE5vdmVsIEMtbWV0YWJvbGlzbSByZWd1bGF0b3IKLSBMaWdodDoKICAtIGBzc3IyMTQyYCB5Y2YxOSwgc2hvcnQgdW5rbm93biBwcm90ZWluLCBpbnRlcmFjdHMgd2l0aCBwc2JPIGFuZCBUYXQgbWVtYnJhbmUgcHJvdGVpbiBpbnNlcnRpb24gc3lzdGVtLAogIC0gYHNscjA5NjNgIHNpciwgc3VsZml0ZSByZWR1Y3Rhc2UsIGZlcnJlZG94aW4gSDJPICsgSFMgKyBmZXJyZWRveGluIDwtPiBIKyArIHJlZHVjZWQgZmVycmVkb3hpbiArIHN1bGZpdGUsCiAgICBzdHJvbmdseSBpbnRlcmFjdHMgd2l0aCBvdGhlciBwcm90ZWlucyBpbiBzdWxmdXIgbWV0YWJvbGlzbSwgc3BlY2lmaWNhbGx5IHJlbGF0ZWQgdG8gY29mYWN0b3IgYmlvc3ludGhlc2lzLCAKICAgIGNvYmFsYW1pbiAodml0YW1pbiBCMTIpIGFuZCAgICBzaXJvaGVtZQotIExpZ2h0LCBtaXhvdHJvcGh5LCBoZXRlcm90cm9waHk6IGNsdXN0ZXIgb2YgcGhvdG9zeW50aGVzaXMgcmVsYXRlZCBnZW5lcyBpbmNyZWFzZSBmaXRuZXNzIHdoZW4gS09lZDogYXBjQSxELEUsIHBzYkIsQyxECi0gQ2FyYm9uOgogIC0gYHNsbDAyMTdgIFB1dGF0aXZlIGRpZmxhdmluIGZsYXZvcHJvdGVpbiBBMiAoZGZhMiksIEtPIG5lZ2F0aXZlbHkgY29ycmVsYXRlZCB3aXRoIGZpdG5lc3Mgd2l0aCBDLCBwb3NpdGl2ZSB3aXRoICtGTAogIC0gYHNsbDAyMThgIHNhbWUgYmVoYXZpb3IgYXMgZGZhMiwgaW50ZXJhY3RzIHdpdGggZGZhMiw0LCBjb250cmlidXRlcyB0byBQU0lJIHN0YWJpbGl6YXRpb24sIAogICAgIFtCZXJzYW5pbmkgZXQgYWwuLCAyMDE3XShodHRwczovL3B1Ym1lZC5uY2JpLm5sbS5uaWguZ292LzI3OTI4ODI0LykuIE5lZ2F0aXZlIGZpdG5lc3Mgc2NvcmUKICAgICBwcm9iYWJseSBzaWRlIGVmZmVjdCBvZiBkb3duc3RyZWFtIEtEIG9mIGBzbGwwMjE5YCAoRmx2MikuIFRoYXQgc2hvd3MgZXNzZW50aWFsbHkgc2FtZSBwYXR0ZXJuIGFzCiAgICAgYHNsbDAyMTdgIGFuZCBgc2xsMDIxOGAgYnV0IHdlYWtlciBlZmZlY3Qgb24gZml0bmVzcy4KCiMjIExpc3Qgb2YgZ2VuZXMgd2l0aCBzdHJvbmcgZml0bmVzcyBjb3JyZWxhdGlvbgoKVGhlIHRhYmxlIHdpdGggbGluZWFyIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGFuZCBwLXZhbHVlcyBpcyByZXNoYXBlZCB0byBsb25nIGZvcm1hdCBmb3IgYmV0dGVyIHJlYWRhYmlsaXR5LiBUaGUgYGthYmxlRXh0cmFgIHBhY2thZ2UgaXMgdXNlZCB0byBjb2xvciBjZWxscyBmb3IgZWFzaWVyIHJlY29nbml0aW9uLiBUaGVuIHdlIHN1YnNldCB0aGUgdGFibGUgZm9yIGVhY2ggdHJlYXRtZW50IGluIG9yZGVyIHRvIHNwb3QgdGhlIG1vc3QgaW50ZXJlc3RpbmcgZ2VuZXMuCgpgYGB7cn0KZGZfbGlucmVnX3dpZGUgPC0gZGZfbGlucmVnICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB0cmVhdG1lbnQsIHZhbHVlc19mcm9tID0gY29lZmZpY2llbnQpICU+JQogIGxlZnRfam9pbihzZWxlY3QoZGZfZ2VuZSwgbG9jdXMsIHNnUk5BX3RhcmdldCkgJT4lIGRpc3RpbmN0LCBieSA9ICJsb2N1cyIpICU+JQogIHNlbGVjdCgtbWF0Y2hlcygiaW50ZXJjZXB0IikpICU+JQogIGZpbHRlcihpZl9hbnkobWF0Y2hlcygiXihjYXJifGxpZ2h0fFxcLXxcXCspIiksIH4gYWJzKC4pID4gMikpICU+JQogIG11dGF0ZShhY3Jvc3MobWF0Y2hlcygiY2FyYnxsaWdodHxcXC18XFwrIiksIH4gcm91bmQoLiwgMykpKSAlPiUgCiAgdW5ncm91cCAlPiUgc2VsZWN0KHNnUk5BX3RhcmdldCwgbG9jdXMsIG1hdGNoZXMoIi4iKSkKCmNvbG9yX3RhYmxlIDwtIGZ1bmN0aW9uKGRmLCB2YXJpYWJsZSkgewogIGZpbHRlcihkZiwgYWJzKC5kYXRhW1t2YXJpYWJsZV1dKSA+IDIpICU+JSAKICBzZWxlY3QobWF0Y2hlcygiXihzZ3xsb2N8cl9zfGNhcmJ8bGlnaHR8XFwtfFxcKykiKSB8IGFsbF9vZihwYXN0ZTAoInB2YWxfIiwgdmFyaWFibGUpKSkgJT4lCiAgYXJyYW5nZShkZXNjKC5kYXRhW1t2YXJpYWJsZV1dKSkgJT4lCiAgbXV0YXRlKGFjcm9zcygzOjgsIH4gY2VsbF9zcGVjKC4sICJodG1sIiwgY29sb3IgPSAid2hpdGUiLAogICAgICBiYWNrZ3JvdW5kID0gc3BlY19jb2xvciguLCBvcHRpb24gPSAiRSIsIHNjYWxlID0gYygtNS41LCA1LjUpKSwKICAgICAgYm9sZCA9IFRSVUUpKSkgJT4lCiAga2JsKGZvcm1hdCA9ICJodG1sIiwgZXNjYXBlID0gRikgJT4lCiAga2FibGVfcGFwZXIoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gRikKfQpgYGAKCgpgYGB7cn0KZGZfbGlucmVnX3dpZGUgJT4lIGNvbG9yX3RhYmxlKCJjYXJib24iKQpgYGAKCmBgYHtyfQpkZl9saW5yZWdfd2lkZSAlPiUgY29sb3JfdGFibGUoImxpZ2h0IikKYGBgCgpgYGB7cn0KZGZfbGlucmVnX3dpZGUgJT4lIGNvbG9yX3RhYmxlKCItTiIpCmBgYAoKYGBge3J9CmRmX2xpbnJlZ193aWRlICU+JSBjb2xvcl90YWJsZSgiK0ZMIikKYGBgCgpgYGB7cn0KZGZfbGlucmVnX3dpZGUgJT4lIGNvbG9yX3RhYmxlKCIrRyIpCmBgYAoKYGBge3J9CmRmX2xpbnJlZ193aWRlICU+JSBjb2xvcl90YWJsZSgiK0QiKQpgYGAKQmFzZWQgb24gdGhlIG11bHRpcGxlIGxpbmVhciBtb2RlbCBjb3JyZWxhdGlvbnMsIHdlIGNhbiB0cnkgdG8gZXh0cmFjdCBhIHNob3J0bGlzdCBvZiB0aGUgbW9zdCBpbnRlcmVzdGluZyAqKmh5cG90aGV0aWNhbCBnZW5lcyoqLiBUaGVzZSBjb3VsZCB3YXJyYW50IGZ1cnRoZXIgaW52ZXN0aWdhdGlvbnMuCgpgYGB7cn0KbGlzdF90b3BfdW5rbm93bl9oaXRzIDwtIGRmX2xpbnJlZ193aWRlICU+JQogIGxlZnRfam9pbihkZl91bmlwcm90LCBieSA9ICJsb2N1cyIpICU+JQogICMgZmlsdGVyIGJ5IG5hbWU6IG9ubHkgdW5rbm93biBwcm90ZWlucwogIGZpbHRlcigKICAgIGlzLm5hKGdlbmVfbmFtZV9zaG9ydCksCiAgICBzdHJfZGV0ZWN0KHByb3RlaW4sICJbYS16QS1aXXszfVswLTldezR9IHByb3RlaW58VW5jaGFyYWN0ZXJpemVkIikpICU+JQogICMgZmlsdGVyIGJ5IGVmZmVjdDogb25seSBjb3JyZWxhdGlvbiA+IDMKICBmaWx0ZXIoaWZfYW55KG1hdGNoZXMoIl4oY2FyYnxsaWdodHxcXC18XFwrKSIpLCB+IGFicyguKSA+IDMpKSAlPiUKICBhcnJhbmdlKGRlc2Mocl9zcXVhcmVkKSkgJT4lCiAgcHVsbChsb2N1cykKCmRmX2xpbnJlZ193aWRlICU+JSBmaWx0ZXIobG9jdXMgJWluJSBsaXN0X3RvcF91bmtub3duX2hpdHMpICU+JQogIHNlbGVjdCghc3RhcnRzX3dpdGgoInB2YWwiKSwgLXNnUk5BX3RhcmdldCkgJT4lCiAgbXV0YXRlKGFjcm9zcygyOjcsIH4gY2VsbF9zcGVjKC4sICJodG1sIiwgY29sb3IgPSAid2hpdGUiLAogICAgICBiYWNrZ3JvdW5kID0gc3BlY19jb2xvciguLCBvcHRpb24gPSAiRSIsIHNjYWxlID0gYygtNS41LCA1LjUpKSwKICAgICAgYm9sZCA9IFRSVUUpKSkgJT4lCiAga2JsKGZvcm1hdCA9ICJodG1sIiwgZXNjYXBlID0gRikgJT4lCiAga2FibGVfcGFwZXIoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gRikKYGBgCgoKIyMgRXh0cmFjdCBhbmQgYW5hbHl6ZSBpbnRlcmVzdGluZyBnZW5lIGNsdXN0ZXJzCgpUaGUgbGlzdCBhYm92ZSBzaG93cyB0aGUgZ2VuZXMgd2hvc2UgZml0bmVzcyBpcyBtb3N0IHNpZ25pZmljYW50bHkgY29ycmVsYXRlZCB3aXRoIG9uZSBvZiB0aGUgdHJlYXRtZW50cy4KVGhpcyBsaXN0IG9mIGdlbmVzIChsZW5ndGg6IGByIGxlbmd0aChsaXN0X3RvcF91bmtub3duX2hpdHMpYCkgaXMgZXh0cmFjdGVkIGFuZCB0aGVuIHNpbXBseSBmaXRuZXNzIHBlciBjb25kaXRpb24gaXMgcGxvdHRlZCBhcyBhIGhlYXQgbWFwLCBpbiBvcmRlciB0byBjb25maXJtIHRoZSB0cmVuZHMgZnJvbSBmaXR0aW5nIHRoZSBtdWx0aXBsZSBsaW5lciByZWdyZXNzaW9uIG1vZGVscy4gTm90ZSB0aGF0IHRoaXMgbGlzdCBvZiBnZW5lcyB3YXMgbm90IGZpbHRlcmVkIGJ5IGZpdG5lc3MgcC12YWx1ZSwgYnV0IG9ubHkgZGVyaXZlZCBmcm9tIG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uLiBUaGUgZml0bmVzcyBwLXZhbHVlIGlzIGluZGljYXRlZCBpbiB0aGUgZmlndXJlIGlmIGBwIDw9IDAuMWAuCgpgYGB7ciwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDQuNX0KcGxvdF91bmtub3duX2dlbmVzIDwtIGRmX2dlbmUgJT4lCiAgZmlsdGVyKGxvY3VzICVpbiUgbGlzdF90b3BfdW5rbm93bl9oaXRzLCB0aW1lID09IDApICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSBmY3RfY2x1c3RlcihzZ1JOQV90YXJnZXQsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcykpICU+JQogIG11dGF0ZShjb25kaXRpb24gPSBmY3RfY2x1c3Rlcihjb25kaXRpb24sIHNnUk5BX3RhcmdldCwgd21lYW5fZml0bmVzcykpICU+JQogIG11dGF0ZSh3bWVhbl9maXRuZXNzID0gd21lYW5fZml0bmVzcyAlPiUgcmVwbGFjZSguLCAuID4gNCwgNCkgJT4lIHJlcGxhY2UoLiwgLiA8IC00LCAtNCkpICU+JQogIG11dGF0ZShwX2xhYmVsID0gaWZfZWxzZShwX2ZpdG5lc3NfYWRqIDw9IDAuMSwgYXMuY2hhcmFjdGVyKHJvdW5kKHBfZml0bmVzc19hZGosIDIpKSwgIiIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjb25kaXRpb24sIHkgPSBzZ1JOQV90YXJnZXQsIGZpbGwgPSB3bWVhbl9maXRuZXNzLCBsYWJlbCA9IHBfbGFiZWwpKSArCiAgZ2VvbV90aWxlKCkgKyBjdXN0b21fdGhlbWUoKSArCiAgZ2VvbV90ZXh0KHNpemUgPSAzKSArCiAgbGFicyh0aXRsZSA9ICJUb3AgdW5rbm93biBnZW5lcywgZml0bmVzcy4iLCB4ID0gIiIsIHkgPSAiIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdCA9IDEpKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IGMoY3VzdG9tX2NvbG9yc1sxXSwgZ3JleSgwLjkpLCBjdXN0b21fY29sb3JzWzJdKSwKICAgIGxpbWl0cyA9IGMoLTQsIDQpKQoKcHJpbnQocGxvdF91bmtub3duX2dlbmVzKQpgYGAKKipTdW1tYXJ5KioKCi0gYHNsbDA4NzdgIC0gNDU2IEFBLiBLRCBoYXMgaGlnaGVyIGZpdG5lc3MgX29ubHlfIGluIEhDLExMLiBNaXRpZ2F0ZXMgbGlnaHQgbGltaXRhdGlvbj8KLSBgc2xsMDQ4MWAgLSAxNTUgQUEuIEtEIGhhcyBoaWdoZXIgZml0bmVzcyBpbiArRyBjb25kaXRpb25zIGFuZCBsb3dlciBmaXRuZXNzIGluIEhMLiBNZW1icmFuZSBsb2NhbGl6YXRpb24uIE5lZ2F0aXZlbHkgcmVndWxhdGluZyBnbHljb2x5c2lzPwotIGBzc3IzNTMyYCAtICA4MCBBQS4gS0QgbG93ZXIgZml0bmVzcyBvbiBOLWxpbWl0YXRpb24gYW5kIEMtbGltaXRhdGlvbiAoTEMtSEwgY29tYmluYXRpb25zKS4gU2FtZSBvcGVyb24gYXMgZ2x1dGFtaW5hc2UgZ2xzQSAoc2xyMjA3OSwgY2F0YWx5emVzIGRlYW1pbmF0aW9uIG9mIGdsbiAtLT4gZ2x1KSwgcmVndWxhdG9yeSwgaW52b2x2ZWQgaW4gTiBtZXRhYm9saXNtPwotIGBzbHIxMTAyYCAtIDg1MyBBQS4gS0QgaGFzIGxvd2VyIGZpdG5lc3Mgb24gYWxsIExMIGNvbmRpdGlvbnMuIDQga25vd24gZG9tYWlucywgRkhBIChmb3JraGVhZC1hc3NvY2lhdGVkIGRvbWFpbiBpcyBhIHBob3NwaG9wZXB0aWRlIHJlY29nbml0aW9uIGRvbWFpbiBmb3VuZCBpbiBtYW55IHJlZ3VsYXRvcnkgcHJvdGVpbnMpLCBQQVMgKHNpZ25hbGluZywgb2Z0ZW4gaW52b2x2ZWQgaW4gY2lyY2FkaWFuIHByb3RlaW5zLCBkZXRlY3QgdGhlaXIgc2lnbmFsIGJ5IHdheSBvZiBhbiBhc3NvY2lhdGVkIGNvZmFjdG9yIGxpa2UgaGVtZSwgZmxhdmluKSwgR0dERUYgKGludm9sdmVkIGluIHNpZ25hbCB0cmFuc2R1Y3Rpb24sIGxpa2VseSB0byBjYXRhbHl6ZSBzeW50aGVzaXMgb3IgaHlkcm9seXNpcyBvZiBjeWNsaWMgZGlndWFueWxhdGUgYy1kaUdNUCksIEVBTCAoc2hvd24gdG8gc3RpbXVsYXRlIGRlZ3JhZGF0aW9uIG9mIGEgc2Vjb25kIG1lc3NlbmdlciwgY3ljbGljIGRpLUdNUCwgY2FuZGlkYXRlIGZvciBhIGRpZ3VhbnlsYXRlIHBob3NwaG9kaWVzdGVyYXNlIGZ1bmN0aW9uLiBUb2dldGhlciB3aXRoIHRoZSBHR0RFRiBkb21haW4sIEVBTCBtaWdodCBiZSBpbnZvbHZlZCBpbiByZWd1bGF0aW5nIGNlbGwgc3VyZmFjZSBhZGhlc2l2ZW5lc3MgaW4gYmFjdGVyaWEpLiBTb3VyY2U6IEludGVyUHJvLiBFbWJlZGRlZCBpbiBhIFt0aWdodCBuZXR3b3JrXShodHRwczovL3N0cmluZy1kYi5vcmcvbmV0d29yay8xMTQ4LjE2NTE4MzQpIG9mIGludGVyYWN0aW5nIHByb3RlaW5zIGFsbCBpbnZvbHZlZCBpbiBjaHJvbW9waG9yZSBiaW9zeW50aGVzaXMvbWF0dXJhdGlvbi4KLSBgc2xsMTM3OGAgLSAzMDAgQUEuICBLRCBoYXMgbG93ZXIgZml0bmVzcyBvbiBhbGwgTEwgY29uZGl0aW9ucy4gTWVtYnJhbmUgYXNzb2NpYXRlZCBwcm90ZWluPyBJbiBTVFJJTkcsIHBvdGVudGlhbCBpbnRlcmFjdGlvbiB3aXRoIFBic0ExIGFuZCBQYnNBMiAoSGVtZSBveHlnZW5hc2UgMSBhbmQgMikuIFBvdGVudGlhbGx5IGltcG9ydGFudCBmb3IgY2hsb3JvcGh5bGwgb3IgaGVtZSBiaW9zeW50aGVzaXMgLS0+IHdvdWxkIGV4cGxhaW4gaW1wb3J0YW5jZSBmb3IgcGhvdG9zeW50aGVzaXMgaW4gTEwgY29uZGl0aW9uLgotIGBzbHIxNTA1YCAtIDE5OCBBQS4gRml0bmVzcyBwcm9maWxlIGFzIGFib3ZlLiBObyB1c2VmdWwgaW5mb3JtYXRpb24uCi0gYHNsbDYwNTVgIC0gMTUyIEFBLiBGaXRuZXNzIHByb2ZpbGUgYXMgYWJvdmUuIE11bHRpdWJpcXVpdGluIGRvbWFpbiwgaW52b2x2ZWQgaW4gcHJvdGVpbiBtb2RpZmljYXRpb24vZGVncmFkYXRpb24gb2YgUFMgcHJvdGVpbnM/Ci0gYHNscjE5OTBgIC0gMjQwIEFBLCA1IFRNIGRvbWFpbnMuIEtEIGhpZ2hlciBmaXRuZXNzIGluIHBob3RvaGV0ZXJvdHJvcGh5LCBsb3dlciBmaXRuZXNzIGluIGFsbCBIQy9MTCBjb25kaXRpb25zLiBTb21ldGhpbmcgaW1wb3J0YW50IGZvciBwaG90b3N5c3RlbXM/IFNvbWV0aGluZyB0aGF0IHdhc3RlcyBlLSBpbiBwaG90b2hldGVyb3Ryb3BoaWMgY29uZGl0aW9ucz8KCioqQXBjIGFuZCBjcGMgcmVwcmVzc2lvbiBtdXRhbnRzKiogZW5jb2RpbmcgcGh5Y29iaWxpc29tZXMgYXJlIGFsc28gZW5yaWNoZWQgaW4gaGlnaCBsaWdodAoKCmBgYHtyLCBmaWcud2lkdGggPSAzLjUsIGZpZy5oZWlnaHQgPSA0LjV9CnBsb3Rfc2dSTkFzX3BoeWNvYmlsIDwtIGRmX2dlbmUgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QoZ2VuZV9uYW1lLCAiW2FjXXBjIiksIHRpbWUgPT0gMCkgJT4lCiAgbXV0YXRlKHdtZWFuX2ZpdG5lc3MgPSB3bWVhbl9maXRuZXNzICU+JSByZXBsYWNlKC4sIC4gPiA0LCA0KSAlPiUgcmVwbGFjZSguLCAuIDwgLTQsIC00KSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gY29uZGl0aW9uLCB5ID0gZmN0X3JldihzZ1JOQV90YXJnZXQpLCBmaWxsID0gd21lYW5fZml0bmVzcykpICsKICBnZW9tX3RpbGUoKSArIGN1c3RvbV90aGVtZSgpICsKICBsYWJzKHRpdGxlID0gIkFwYy9DcGMgcmVwcmVzc2lvbiBtdXRhbnRzIiwgeCA9ICIiLCB5ID0gIiIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3QgPSAxKSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBjKGN1c3RvbV9jb2xvcnNbMV0sIGdyZXkoMC45KSwgY3VzdG9tX2NvbG9yc1syXSksCiAgICBsaW1pdHMgPSBjKC00LCA0KSkKCnByaW50KHBsb3Rfc2dSTkFzX3BoeWNvYmlsKQpgYGAKCgojIERpcmVjdCBjb21wYXJpc29uIG9mIGdlbmUgZml0bmVzcwoKIyMgRml0bmVzcyBvZiBhbGwgY29uZGl0aW9ucyB2cyBlYWNoIG90aGVyCgpXZSBjYW4gcGxvdCBzZWxlY3RlZCBjb25kaXRpb25zIGFnYWluc3QgZWFjaCBvdGhlciBhbmQgYWRkIGdlbmUgbGFiZWxzIGluIG9yZGVyIHRvIGZpbmQgb3IgY29uZmlybSBwYXJ0aWN1bGFyIHBhdHRlcm5zLgoKYGBge3J9Cm1ha2VfZml0bmVzc19wbG90IDwtIGZ1bmN0aW9uKGRhdGEsIHZhcnMsIHRpdGxlID0gTlVMTCkgewogICMgcHJlcGFyZSBkYXRhIGZvciB0d28gIHZhcmlhYmxlcyBlYWNoCiAgZGF0YSAlPiUgdW5ncm91cCAlPiUKICAgIGZpbHRlcihjb25kaXRpb24gJWluJSB2YXJzLCBzZ1JOQV90eXBlID09ICJnZW5lIikgJT4lCiAgICBzZWxlY3QobG9jdXMsIHNnUk5BX3RhcmdldCwgY29uZGl0aW9uLCB3bWVhbl9maXRuZXNzKSAlPiUgZGlzdGluY3QgJT4lCiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gY29uZGl0aW9uLCB2YWx1ZXNfZnJvbSA9IHdtZWFuX2ZpdG5lc3MpICU+JQogICAgbXV0YXRlKAogICAgICBkZml0ID0gZ2V0KHZhcnNbMV0pIC0gZ2V0KHZhcnNbMl0pLAogICAgICBzaWduaWZpY2FudCA9ICFiZXR3ZWVuKGRmaXQsIHF1YW50aWxlKGRmaXQsIHByb2JzID0gYygwLjAwMykpLAogICAgICAgIHF1YW50aWxlKGRmaXQsIHByb2JzID0gYygwLjk5NykpKSwKICAgICAgc2dSTkFfdGFyZ2V0ID0gaWZfZWxzZShzaWduaWZpY2FudCwgc2dSTkFfdGFyZ2V0LCAiIikpICU+JQogICAgCiAgICAjIHBsb3QKICAgIGdncGxvdChhZXMoeCA9IGdldCh2YXJzWzFdKSwgeSA9IGdldCh2YXJzWzJdKSwgCiAgICAgIGNvbG9yID0gc2lnbmlmaWNhbnQsIGxhYmVsID0gc2dSTkFfdGFyZ2V0KSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMSkgKyBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gMCkgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2wgPSBncmV5KDAuNSksIGx0eSA9IDIsIHNpemUgPSAwLjgpICsKICAgIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDQsIHNsb3BlID0gMSwgY29sID0gZ3JleSgwLjUpLCBsdHkgPSAyLCBzaXplID0gMC44KSArCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAtNCwgc2xvcGUgPSAxLCBjb2wgPSBncmV5KDAuNSksIGx0eSA9IDIsIHNpemUgPSAwLjgpICsKICAgIGdlb21fdGV4dF9yZXBlbChzaXplID0gMywgbWF4Lm92ZXJsYXBzID0gNTApICsKICAgIGxhYnModGl0bGUgPSB0aXRsZSwgeCA9IHZhcnNbMV0sIHkgPSB2YXJzWzJdKSArCiAgICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoLTksIDUpLCB5bGltID0gYygtOSwgNSkpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKGdyZXkoMC41KSwgY3VzdG9tX2NvbG9yc1syXSkpCn0KCiMgYnJvd3NlIHRocm91Z2ggYWxsIHBvc3NpYmxlIGNvbmRpdGlvbiBjb21iaW5hdGlvbnM7CiMgd2UgbmVlZCBhIGhlbHBlciBmdW5jdGlvbiB0aGF0IGRldGVjdHMgZHVwbGljYXRlZCBjb21iaW5hdGlvbnMKZHVwbGljYXRlZF8ydmVjIDwtIGZ1bmN0aW9uKHgsIHkpIHsKICB4eSA9IHBhc3RlKHgsIHkpOyB5eCA9IHBhc3RlKHksIHgpCiAgc2FwcGx5KHh5LCBmdW5jdGlvbih4dmFsKSB7CiAgICB3aGljaCh4dmFsID09IHl4KSA8PSB3aGljaCh4dmFsID09IHh5KQogIH0pCn0KCmxpc3RfY29uZGl0aW9uX3BhaXJzIDwtIGxhcHBseSgKICB1bmlxdWUoZGZfZ2VuZSRjb25kaXRpb24pICU+JSBleHBhbmRfZ3JpZCh4ID0gLiwgeSA9IC4pICU+JQogICAgZmlsdGVyKCFkdXBsaWNhdGVkXzJ2ZWMoeCwgeSkpICU+JSB0ICU+JSBhcy5kYXRhLmZyYW1lICU+JSBhcy5saXN0LAogIGZ1bmN0aW9uKHZhcikgewogICAgbWFrZV9maXRuZXNzX3Bsb3QoZGZfZ2VuZSwgdmFycyA9IHZhciwKICAgICAgdGl0bGUgPSBwYXN0ZSh2YXIsIGNvbGxhcHNlID0gIiAgLSAgIikpCiAgfQopCgojIGV4cG9ydCBpbWFnZXMKaW52aXNpYmxlKGNhcHR1cmUub3V0cHV0KAogIGxhcHBseShsaXN0X2NvbmRpdGlvbl9wYWlycywgZnVuY3Rpb24ocGwpIHsKICAgIHBsX25hbWUgPC0gcGFzdGUwKCIuLi9maWd1cmVzL3BhaXJ3aXNlX2NvbXBhcmlzb25zL3Bsb3RfIiwgcGwkbGFiZWxzJHgsICJfIiwgcGwkbGFiZWxzJHksICIucG5nIikKICAgIHBuZyhmaWxlbmFtZSA9IHBsX25hbWUsIHdpZHRoID0gODAwLCBoZWlnaHQgPSA4MDAsIHJlcyA9IDEyMCkKICAgIHByaW50KHBsKQogICAgZGV2Lm9mZigpCiAgfSkKKSkKYGBgCgoKYGBge3IsIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQgPSA1fQojIGV4YW1wbGUgb2YgZmlyc3QgNCBjb21iaW5hdGlvbnMKbGlzdF9jb25kaXRpb25fcGFpcnNbMTo0XQpgYGAKCiMgRGlmZmVyZW50aWFsIGZpdG5lc3Mgb2Ygc2VsZWN0ZWQgZ2VuZSBzZXRzCgojIyBDZW50cmFsIGNhcmJvbiBtZXRhYm9saXNtCgpUbyBwbG90IGdlbmUgZml0bmVzcyBmb3IgdGhlIGVuenltZXMgb2YgY2VudHJhbCBjYXJib24gbWV0YWJvbGlzbSwgd2UgdXNlIHRoZSBjb21wbGV0ZSBsaXN0IG9mIGVuenltZXMgYW5kIHRoZSBnZW5lcyB0aGF0IHRoZXkgYXJlIG1hcHBlZCB0byAob2J0YWluZWQgZnJvbSBLRUdHKS4gV2UgY2FuIGV4dHJhY3QgZ2VuZSBzZXRzIGZvciBzcGVjaWZpYyBwYXRod2F5cyBhbmQgcGxvdCBmaXRuZXNzLiBXZSBzdGFydCB3aXRoIGdseWNvbHlzaXMgYW5kIENhbHZpbiBjeWNsZSBlbnp5bWVzLgoKYGBge3J9Cmxpc3RfY2VudHJhbF9tZXRfcGF0aHdheXMgPC0gYygKICAiR2x5Y29seXNpcyAvIEdsdWNvbmVvZ2VuZXNpcyIsCiAgIlBlbnRvc2UgcGhvc3BoYXRlIHBhdGh3YXkiLAogICJDYXJib24gZml4YXRpb24gaW4gcGhvdG9zeW50aGV0aWMgb3JnYW5pc21zIiwKICAiUGhvdG9zeW50aGVzaXMiLAogICJDaXRyYXRlIGN5Y2xlIChUQ0EgY3ljbGUpIiwKICAiUHlydXZhdGUgbWV0YWJvbGlzbSIsCiAgIkdseW94eWxhdGUgYW5kIGRpY2FyYm94eWxhdGUgbWV0YWJvbGlzbSIKKQpgYGAKCgpgYGB7cn0KcGxvdF9nZW5lX2ZpdG5lc3MgPC0gZnVuY3Rpb24oZGYsIHB3ID0gTlVMTCwgZ2VuZSA9IE5VTEwsCiAgdGl0bGUgPSBOVUxMLCBuY29sID0gOCwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpIHsKICBkZiA8LSBkZiAlPiUgZmlsdGVyKHRpbWUgPT0gMCkKICBpZiAoIWlzLm51bGwocHcpKSB7CiAgICBkZiA8LSBkZiAlPiUgaW5uZXJfam9pbihkZl9rZWdnICU+JSBmaWx0ZXIoa2VnZ19wYXRod2F5ID09IHB3KSAlPiUgc2VsZWN0KGxvY3VzKSwKICAgICAgYnkgPSAibG9jdXMiKQogICAgdGl0bGUgPC0gcHcKICB9IGVsc2UgaWYgKCFpcy5udWxsKGdlbmUpKSB7CiAgICBkZiA8LSBkZiAlPiUgZmlsdGVyKGxvY3VzICVpbiUgZ2VuZSkKICB9CiAgCiAgZ2dwbG90KGRmLCBhZXMoeCA9IGNvbmRpdGlvbiwgeSA9IHdtZWFuX2ZpdG5lc3MsIAogICAgeW1pbiA9IHdtZWFuX2ZpdG5lc3Mtc2RfZml0bmVzcywgCiAgICB5bWF4ID0gd21lYW5fZml0bmVzcytzZF9maXRuZXNzLAogICAgZmlsbCA9IGNvbmRpdGlvbiwKICAgIGNvbG9yID0gY29uZGl0aW9uLAogICAgbGFiZWwgPSBpZl9lbHNlKHBfZml0bmVzc19hZGogPD0gMC4wMSwgIioiLCAiIikpKSArCiAgICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gMC42KSArCiAgICBnZW9tX2Vycm9yYmFyKHBvc2l0aW9uID0gImRvZGdlIiwgd2lkdGggPSAwLjYsIHNpemUgPSAxKSArCiAgICBnZW9tX3RleHQoc2l6ZSA9IDUsIGFlcyh5ID0gbWFwcGx5KEZVTiA9IGZ1bmN0aW9uKHgsIHkpIHsKICAgICAgaWYgKHggPCAwKSB4LXktMgogICAgICBlbHNlIHgreSswLjMKICAgIH0sIHdtZWFuX2ZpdG5lc3MsIHNkX2ZpdG5lc3MpKSkgKwogICAgY3VzdG9tX3RoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGxlZ2VuZC5wb3NpdGlvbiwgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjQsICJjbSIpKSArIAogICAgbGFicyh0aXRsZSA9IHRpdGxlLCB4ID0gIiIsIHkgPSAiZml0bmVzcyIpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvclJhbXBQYWxldHRlKGN1c3RvbV9jb2xvcnNbMTo1XSkoMTEpKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sb3JSYW1wUGFsZXR0ZShjdXN0b21fY29sb3JzWzE6NV0pKDExKSkgKwogICAgZmFjZXRfd3JhcCh+IHNnUk5BX3RhcmdldCwgbmNvbCA9IG5jb2wsIGRyb3AgPSBGQUxTRSkKfQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDZ9CnByaW50KHBsb3RfZ2VuZV9maXRuZXNzKGRmX2dlbmUsIHB3ID0gbGlzdF9jZW50cmFsX21ldF9wYXRod2F5c1tbMV1dKSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDV9CnByaW50KHBsb3RfZ2VuZV9maXRuZXNzKGRmX2dlbmUsIHB3ID0gbGlzdF9jZW50cmFsX21ldF9wYXRod2F5c1tbMl1dKSkKYGBgCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDV9CnByaW50KHBsb3RfZ2VuZV9maXRuZXNzKGRmX2dlbmUsIHB3ID0gbGlzdF9jZW50cmFsX21ldF9wYXRod2F5c1tbM11dKSkKYGBgCgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA0fQpwcmludChwbG90X2dlbmVfZml0bmVzcyhkZl9nZW5lLCBwdyA9IGxpc3RfY2VudHJhbF9tZXRfcGF0aHdheXNbWzVdXSkpCmBgYAoKIyMgR2VuZSBmaXRuZXNzIGluIG1peG90cm9waHkgYW5kIGhldGVyb3Ryb3BoeQoKVXNpbmcgW2ZsdWN0dWF0b3JdKGh0dHBzOi8vZ2l0aHViLmNvbS9tLWphaG4vZmx1Y3R1YXRvciksIHdlIGNhbiBpbXBvcnQgYSBjdXN0b20gbWV0YWJvbGljIG1hcCBmb3IgX1N5bmVjaG9jeXN0aXNfIHNwLiBQQ0MgNjgwMywgYW5kIG92ZXJsYXkgcHVibGlzaGVkIGZsdXhlcyB0aGF0IHdlcmUgbWVhc3VyZWQgd2l0aCBMQy1NUyB1c2luZyBpc290b3BpY2FsbHkgbGFiZWxsZWQgY2FyYm9uIHNvdXJjZXMgKFtOYWthamltYSBldCBhbC4sIDIwMTRdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDkzL3BjcC9wY3UwOTEpKS4KCkZsdWN0dWF0b3IgY2FuIGJlIGluc3RhbGxlZCB1c2luZyBhIGZ1bmN0aW9uIGZyb20gYGRldnRvb2xzYDoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigibS1qYWhuL2ZsdWN0dWF0b3IiKQpgYGAKCldlIGltcG9ydCB0aGUgbWV0YWJvbGljIGZsdXggZGF0YSBmcm9tIHRoZSBzdXBwbGVtZW50YWwgaXRlbXMgb2YgW05ha2FqaW1hIGV0IGFsLiwgMjAxNF0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwOTMvcGNwL3BjdTA5MSkuCgpgYGB7cn0KbGlicmFyeShmbHVjdHVhdG9yKQoKIyBpbXBvcnQgZmx1eCBkYXRhCmRmX25ha2FqaW1hX21mYSA8LSByZWFkLmNzdigiLi4vZGF0YS9pbnB1dC9OYWthamltYTIwMTRfbWV0YWJvbGljX2ZsdXhlcy5jc3YiKQoKIyBnZW5lcmF0ZSBzdHJva2Ugd2lkdGggYW5kIGNvbG9yCmRmX25ha2FqaW1hX21mYSA8LSBkZl9uYWthamltYV9tZmEgJT4lCiAgbXV0YXRlKAogICAgc3Ryb2tlX3dpZHRoID0gMC4zICsgKDAuNypzcXJ0KGFicyhmbHV4KSkpLAogICAgc3Ryb2tlX2NvbG9yID0gYWJzKGZsdXgpICU+JSB7MSsoLi9tYXgoLikpKjl9ICU+JSByb3VuZCwKICAgIHN0cm9rZV9jb2xvcl9yZ2IgPSAgY29sb3JSYW1wUGFsZXR0ZShjdXN0b21fY29sb3JzW2MoNSwyLDEpXSkoMTApW3N0cm9rZV9jb2xvcl0pCmBgYAoKVGhlIG5leHQgc3RlcCBpcyB0byBvdmVybGF5IHRoZSBmbHV4ZXMuIFdlIGdlbmVyYXRlIHR3byB0eXBlcyBvZiBtYXBzLCBtaXhvdHJvcGh5IGFuZCBwaG90b2hldGVyb3Ryb3BoeS4KVGhlIHN0cm9rZSB3aWR0aCBhbmQgY29sb3IgZm9yIGFsbCByZWFjdGlvbnMgaXMgc2V0IGJ5IHRoZSBmbHV4IG1hZ25pdHVkZS4KCmBgYHtyfQpmb3IgKGNvbmQgaW4gYygibWl4b3Ryb3BoIiwgInBob3RvaGV0ZXJvdHJvcGgiKSkgewogICMgaW1wb3J0IG1hcCAKICBTVkdfdGVtcGxhdGUgPC0gcmVhZF9zdmcoIi4uL2RhdGEvaW5wdXQvbWFwX2NlbnRyYWxfbWV0YWJvbGlzbV9zeW4uc3ZnIikKICAKICAjIHNldCBzdHJva2Ugb24gU1ZHIG1hcAogIFNWR19taXggPC0gc2V0X2F0dHJpYnV0ZXMoU1ZHX3RlbXBsYXRlLAogICAgbm9kZSA9IGZpbHRlcihkZl9uYWthamltYV9tZmEsIGNvbmRpdGlvbiA9PSBjb25kKSRyZWFjdGlvbiwKICAgIGF0dHIgPSAic3R5bGUiLAogICAgcGF0dGVybiA9ICJzdHJva2Utd2lkdGg6WzAtOV0rXFwuWzAtOV0rIiwKICAgIHJlcGxhY2VtZW50ID0gcGFzdGUwKCJzdHJva2Utd2lkdGg6IiwKICAgICAgZmlsdGVyKGRmX25ha2FqaW1hX21mYSwgY29uZGl0aW9uID09IGNvbmQpJHN0cm9rZV93aWR0aCkpCiAgCiAgIyBzZXQgY29sb3IKICBTVkdfbWl4IDwtIHNldF9hdHRyaWJ1dGVzKFNWR19taXgsCiAgICBub2RlID0gZmlsdGVyKGRmX25ha2FqaW1hX21mYSwgY29uZGl0aW9uID09IGNvbmQpJHJlYWN0aW9uLAogICAgYXR0ciA9ICJzdHlsZSIsCiAgICBwYXR0ZXJuID0gInN0cm9rZTojYjNiM2IzIiwKICAgIHJlcGxhY2VtZW50ID0gcGFzdGUwKCJzdHJva2U6IiwKICAgICAgZmlsdGVyKGRmX25ha2FqaW1hX21mYSwgY29uZGl0aW9uID09IGNvbmQpJHN0cm9rZV9jb2xvcl9yZ2IpKQogIAogICMgc2V0IGFycm93IGRpcmVjdGlvbmFsaXR5CiAgU1ZHX21peCA8LSBzZXRfYXR0cmlidXRlcyhTVkdfbWl4LAogICAgbm9kZSA9IGZpbHRlcihkZl9uYWthamltYV9tZmEsIGNvbmRpdGlvbiA9PSBjb25kLCBmbHV4IDwgMCkkcmVhY3Rpb24sCiAgICBhdHRyID0gInN0eWxlIiwKICAgIHBhdHRlcm4gPSAibWFya2VyLWVuZDp1cmxcXCgjbWFya2VyWzAtOV0qXFwpOyIsCiAgICByZXBsYWNlbWVudCA9ICIiKQogIAogIFNWR19taXggPC0gc2V0X2F0dHJpYnV0ZXMoU1ZHX21peCwKICAgIG5vZGUgPSBmaWx0ZXIoZGZfbmFrYWppbWFfbWZhLCBjb25kaXRpb24gPT0gY29uZCwgZmx1eCA+IDApJHJlYWN0aW9uLAogICAgYXR0ciA9ICJzdHlsZSIsCiAgICBwYXR0ZXJuID0gIm1hcmtlci1zdGFydDp1cmxcXCgjbWFya2VyWzAtOV0qXFwpOyIsCiAgICByZXBsYWNlbWVudCA9ICIiKQogIAogIHdyaXRlX3N2ZyhTVkdfbWl4LCBmaWxlID0gcGFzdGUwKCIuLi9kYXRhL291dHB1dC9tYXBfIiwgY29uZCwgInkuc3ZnIikpCn0KYGBgCgpNZXRhYm9saWMgZmx1eCB3aXRoIG1peG90cm9waHkgfCAgTWV0YWJvbGljIGZsdXggd2l0aCBwaG90b2hldGVyb3Ryb3BoeQo6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS06CiFbXSguLi9kYXRhL291dHB1dC9tYXBfbWl4b3Ryb3BoeS5zdmcpICB8ICAhW10oLi4vZGF0YS9vdXRwdXQvbWFwX3Bob3RvaGV0ZXJvdHJvcGh5LnN2ZykKCgpOb3cgd2UgcGxvdCBmaXRuZXNzIG9mIGNlbnRyYWwgY2FyYm9uIG1ldGFib2xpc20gZ2VuZXMgZm9yIHR3byBvciB0aHJlZSBzZWxlY3RlZCBjb25kaXRpb25zLiBUaGVzZSB3aWxsIGJlIGFkZGVkIHRvIHRoZSBtZXRhYm9saWMgbWFwIG1hbnVhbGx5LiBUaGUgbWl4b3Ryb3BoaWMgY29uZGl0aW9ucyBgTEMsIExMLCArR2AgYW5kIGBIQywgTEwsICtHYCB0dXJuZWQgb3V0IHRvIGJlIHZlcnkgc2ltaWxhci4KCmBgYHtyfQpkZl9jZW50cmFsY2FyYiA8LSB0aWJibGUoCiAgbG9jdXMgPSBjKAogICAgIyBFTVAKICAgICJzbGwwNTkzIiwgInNscjAzMjkiLCAic2xsMDMyOSIsICJzbHIwOTUyIiwgInNscjIwOTQiLAogICAgInNsbDAwMTgiLCAic2xyMDk0MyIsICJzbHIwNzgzIiwgInNscjA4ODQiLCAic2xsMTM0MiIsCiAgICAic2xyMDM5NCIsICJzbHIxOTQ1IiwgInNscjA3NTIiLCAic2xsMTI3NSIsICJzbGwwNTg3IiwKICAgICMgUFBQICsgQ0JCCiAgICAic2xyMTg0MyIsICJzbGwxNDc5IiwgInNscjEzNDkiLCAic2xyMTc5MyIsICJzbGwxMDcwIiwKICAgICJzbGwwODA3IiwgInNscjAxOTQiLCAic3NsMjE1MyIsICJzbGwxNTI1IiwgInNscjAwMTIiLAogICAgInNscjAwMDkiLAogICAgIyBQeXJ1dmF0ZSBtZXRhYm9saXNtCiAgICAic2xsMTg0MSIsICJzbGwxNzIxIiwgInNscjEwOTYiLCAic2xyMTkzNCIsICJzbGwwNDAxIiwKICAgICJzbHIwNzIxIiwgInNsbDA5MjAiLAogICAgIyBUQ0EKICAgICJzbHIwNjY1IiwgInNscjEyODkiLCAic2xyMTA5NiIsICJzbGwxMDIzIiwgInNsbDE1NTciLAogICAgInNscjEyMzMiLCAic2xyMDIwMSIsICJzbGwxNjI1IiwgInNsbDA4MjMiLCAic2xyMDAxOCIsCiAgICAic2xsMDg5MSIpLAogIHJlYWN0aW9uID0gYygKICAgICMgRU1QCiAgICAiSEVYIiwgIkhFWCIsICJQR0kiLCAiRkJQIiwgIkZCUCIsCiAgICAiRkJBIiwgIkZCQSIsICJUUEkiLCAiR0FQREgiLCAiR0FQREgiLAogICAgIlBHSyIsICJQR00iLCAiRU5PIiwgIlBZSyIsICJQWUsiLAogICAgIyBQUFAgKyBDQkIKICAgICJHNlBESCIsICJQR0wiLCAiR05EIiwgIlRBTCIsICJUS1QiLAogICAgIlJQRSIsICJSUEkiLCAiUlBJIiwgIlBSVUsiLCAiUlVCSVNDTyIsCiAgICAiUlVCSVNDTyIsIAogICAgIyBQeXJ1dmF0ZSBtZXRhYm9saXNtCiAgICAiUERIIiwgIlBESCIsICJQREgiLCAiUERIIiwgIkNTIiwgCiAgICAiTUUiLCAiUFBDIiwKICAgICMgVENBCiAgICAiQUNPTlQiLCAiSUNESCIsICJBS0dESCIsICJTVUNPQVMiLCAiU1VDT0FTIiwKICAgICJTVUNEIiwgIlNVQ0QiLCAiU1VDRCIsICJTVUNEIiwgIkZVTSIsCiAgICAiTURIIgogICAgKSkKCmRmX2NlbnRyYWxjYXJiIDwtIGRmX2dlbmUgJT4lIGZpbHRlcigKICAgIHRpbWUgPT0gMCwKICAgIGNvbmRpdGlvbiAlaW4lIGMoIkxDLCBMTCIsICJMQywgTEwsICtHIiwgIkxDLCBMTCwgK0QsICtHIikpICU+JQogIGlubmVyX2pvaW4oZGZfY2VudHJhbGNhcmIsIC4sIGJ5ID0gImxvY3VzIikgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IHBhc3RlMChyZWFjdGlvbiwgIiAoIiwgc2dSTkFfdGFyZ2V0LCAiKSIpICU+JQogICAgZmN0X2lub3JkZXIpICU+JQogIG11dGF0ZShjb25kaXRpb24gPSByZWNvZGUoY29uZGl0aW9uLCBgTEMsIExMYCA9ICJQaG90b3Ryb3BoeSIsIGBMQywgTEwsICtHYCA9ICJNaXhvdHJvcGh5IiwKICAgIGBMQywgTEwsICtELCArR2AgPSAiUGhvdG9oZXRlcm90cm9waHkiKSAlPiUgZmFjdG9yKC4sIHVuaXF1ZSguKVtjKDEsMywyKV0pKSAlPiUKICBtdXRhdGUocGF0aHdheSA9IHJlcChjKCJFTVAiLCAiUFBQICsgQ0JCIiwgIlB5cnV2YXRlIiwgIlRDQSIpLCBjKDQ1LDMzLDIxLDMzKSkpCmBgYAoKCgpgYGB7ciwgZmlnLndpZHRoID0gNy4zLCBmaWcuaGVpZ2h0ID0gN30KcGxvdF9jZW50cmFsY2FyYl9taW5pZmlnIDwtIGRmX2NlbnRyYWxjYXJiICU+JQogIGdyb3VwX2J5KHBhdGh3YXkpICU+JSBncm91cF9zcGxpdCAlPiUKICBsYXBwbHkoZnVuY3Rpb24oZGYpIHsKICAgIGdncGxvdChkZiwgYWVzKHggPSBjb25kaXRpb24sIHkgPSB3bWVhbl9maXRuZXNzLCAKICAgICAgeW1pbiA9IHdtZWFuX2ZpdG5lc3Mtc2RfZml0bmVzcywgCiAgICAgIHltYXggPSB3bWVhbl9maXRuZXNzK3NkX2ZpdG5lc3MsIGZpbGwgPSBjb25kaXRpb24sIGNvbG9yID0gY29uZGl0aW9uKSkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYygwLCAtNSwgLTEwKSwgbGluZXR5cGUgPSAzLCBjb2wgPSBncmV5KDAuNikpICsKICAgIGdlb21fY29sKHBvc2l0aW9uID0gImRvZGdlIiwgd2lkdGggPSAwLjYpICsKICAgIGdlb21fZXJyb3JiYXIocG9zaXRpb24gPSAiZG9kZ2UiLCB3aWR0aCA9IDAuNiwgc2l6ZSA9IDEpICsKICAgIGN1c3RvbV90aGVtZShhc3BlY3QucmF0aW8gPSAxLCBsZWdlbmQucG9zaXRpb24gPSAwKSArIAogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpICsKICAgIGxhYnMoeCA9ICIiLCB5ID0gIiIpICsKICAgIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygtMTEsIDEpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjdXN0b21fY29sb3JzW2MoNSwyLDMpXSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9jb2xvcnNbYyg1LDIsMyldKSArCiAgICBmYWNldF93cmFwKH4gc2dSTkFfdGFyZ2V0LCBuY29sID0gMTEpCiAgfSkKCmdnYXJyYW5nZShucm93ID0gNCwgaGVpZ2h0cyA9ICBjKDAuNCwgMC4yLCAwLjE4NSwgMC4yKSwKICBwbG90X2NlbnRyYWxjYXJiX21pbmlmaWdbWzFdXSwgcGxvdF9jZW50cmFsY2FyYl9taW5pZmlnW1syXV0sCiAgcGxvdF9jZW50cmFsY2FyYl9taW5pZmlnW1szXV0sIHBsb3RfY2VudHJhbGNhcmJfbWluaWZpZ1tbNF1dCikKYGBgCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpzdmcoZmlsZW5hbWUgPSAiLi4vZmlndXJlcy9maWd1cmU0LnN2ZyIsIHdpZHRoID0gNy4zLCBoZWlnaHQgPSA3KQpnZ2FycmFuZ2UobnJvdyA9IDQsIGhlaWdodHMgPSAgYygwLjQsIDAuMiwgMC4xODUsIDAuMiksCiAgcGxvdF9jZW50cmFsY2FyYl9taW5pZmlnW1sxXV0sIHBsb3RfY2VudHJhbGNhcmJfbWluaWZpZ1tbMl1dLAogIHBsb3RfY2VudHJhbGNhcmJfbWluaWZpZ1tbM11dLCBwbG90X2NlbnRyYWxjYXJiX21pbmlmaWdbWzRdXQopCmRldi5vZmYoKQpgYGAKCgojIyBBZGFwdGF0aW9uIHRvIGxpZ2h0IGFuZCBjYXJib24gZXhjZXNzCgpXZSB3aWxsIGxvb2sgYXQgdGhyZWUgZGlmZmVyZW50IHR5cGVzIG9mIHJlZ3VsYXRvcnkgYWRhcHRhdGlvbnM6CgotIGBhcGNgL2BjcGNgYW50ZW5uYSBwcm90ZWlucyAocGh5Y29iaWxpc29tZXMpLCBrbm93biB0byBiZSBhbW9uZyB0aGUgbW9zdCBleHByZXNzZWQgYW5kIHJlZ3VsYXRlZCBnZW5lcyBpbiBjeWFub3MKLSBmbGF2b3Byb3RlaW5zIEZsdjEgKGBzbGwxNTIxYCksIEZsdjIgKGBzbGwwMjE5YCksIEZsdjMgKGBzbGwwNTUwYCksIEZsdjQgKGBzbGwwMjE3YCksIGBzbGwwMjE4YCAoaW4gZmx2Mi80IG9wZXJvbikKLSBsb3cgYWZmaW5pdHkvaGlnaCBmbHV4IHRyYW5zcG9ydGVycyBDaSB0cmFuc3BvcnRlcnM6IGJpY0EgKGBzbGwwODM0YCksIE5ESC1JNCB3aXRoIG5kaEY0LCBENCwgY3VwQiAoYHNsbDAwMjZgLCBgc2xsMDAyN2AsIGBzbHIxMzAyYCkKLSBoaWdoIGFmZmluaXR5L2xvdyBmbHV4IGluZHVjaWJsZSBDaSB0cmFuc3BvcnRlcnM6IEJDVDEvY21wQUIocG9yQilDRCAoYHNscjAwNDAtNDRgKSwgU2J0QS9CIChgc2xyMTUxMmAsIGBzbHIxNTEzYCksIE5ESC1JMyB3aXRoIG5kaEYzLCBuZGhEMywgY3VwQSwgY3VwUyAoYHNsbDE3MzItMzVgKQotIGNhcmJvbiB0cmFuc3BvcnQgcmVndWxhdG9yeSBwcm90ZWluczogY2NtUi9yYmNSIChgc2xsMTU5NGApLCBjbXBSIChgc2xsMDAzMGApLCBjeWFickIxIChgc2xsMDM1OWApLCBjeWFickIyIChgc2xsMDgyMmApCgoKYGBge3J9CnBsb3RfcGh5Y29iaWxpc29tZSA8LSBkZl9nZW5lICU+JSBmaWx0ZXIoc3RyX2RldGVjdChnZW5lX25hbWUsICJbYWNdcGNbQUJDREVGR10iKSkgJT4lCiAgcGxvdF9nZW5lX2ZpdG5lc3MobmNvbCA9IDYsIGxlZ2VuZC5wb3NpdGlvbiA9IDApCgpwbG90X2Zsdl9nZW5lcyA8LSBkZl9nZW5lICU+JSBmaWx0ZXIobG9jdXMgJWluJSBjKCJzbGwxNTIxIiwgInNsbDAyMTkiLCAic2xsMDU1MCIsICJzbGwwMjE3IiwgInNsbDAyMTgiKSkgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IHJlY29kZShzZ1JOQV90YXJnZXQsIGBzbGwxNTIxYCA9ICJGbHYxIChzbGwxNTIxKSIsIGBzbGwwMjE5YCA9ICJGbHYyIChzbGwwMjE5KSIsCiAgICBgc2xsMDU1MGAgPSAiRmx2MyAoc2xsMDU1MCkiLCBgc2xsMDIxN2AgPSAiRmx2NCAoc2xsMDIxNykiKSkgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IGZhY3RvcihzZ1JOQV90YXJnZXQsIGModW5pcXVlKHNnUk5BX3RhcmdldCksICIiKSkpICU+JQogIHBsb3RfZ2VuZV9maXRuZXNzKG5jb2wgPSA2LCBsZWdlbmQucG9zaXRpb24gPSAwKQoKcGxvdF9jYXJib25fdXB0YWtlIDwtIGRmX2dlbmUgJT4lIGZpbHRlcihsb2N1cyAlaW4lIGMoCiAgICAic2xsMDAyNiIsICJzbGwwMDI3IiwgInNscjEzMDIiLAogICAgInNsbDE3MzIiLCAic2xsMTczMyIsICJzbGwxNzM0IiwgInNsbDE3MzUiLCAic2xyMDA0MCIsICJzbHIwMDQxIiwic2xyMDA0MyIsInNscjAwNDQiCiAgKSkgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IHJlY29kZShzZ1JOQV90YXJnZXQsCiAgICBgbnJ0QzJgID0gImNtcEMiLCBgbnJ0RDNgID0gImNtcEQiLAogICAgYHNsbDE3MzRgID0gImN1cEEiLCBgc2xyMTMwMmAgPSAiY3VwQiIsCiAgICBgc2xsMTczNWAgPSAiY3VwUyIsIGBuZGhGMmAgPSAibmRoRjMiCiAgKSkgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IGZhY3RvcihzZ1JOQV90YXJnZXQsIHVuaXF1ZShzZ1JOQV90YXJnZXQpW2MoNCw2LDExLDMsNSw5LDEwLDEsMiw3LDgpXSkpICU+JQogIHBsb3RfZ2VuZV9maXRuZXNzKG5jb2wgPSA2LCBsZWdlbmQucG9zaXRpb24gPSAwKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKC03LjksIDIuNCkpCmBgYAoKRmlndXJlIDMgZHJhZnQ6CgpgYGB7ciwgZmlnLndpZHRoID0gNi4wLCBmaWcuaGVpZ2h0ID0gOC41fQpnZ2FycmFuZ2UobnJvdyA9IDMsIGhlaWdodHMgPSAgYygwLjQ3LCAwLjIsIDAuMzMpLCBsYWJlbHMgPSBMRVRURVJTWzE6M10sIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogIHBsb3RfcGh5Y29iaWxpc29tZSwKICBwbG90X2Zsdl9nZW5lcywKICBwbG90X2NhcmJvbl91cHRha2UKKQpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnN2ZyhmaWxlbmFtZSA9ICIuLi9maWd1cmVzL2ZpZ3VyZTMuc3ZnIiwgd2lkdGggPSA2LjAsIGhlaWdodCA9IDguNSkKZ2dhcnJhbmdlKG5yb3cgPSAzLCBoZWlnaHRzID0gIGMoMC40NywgMC4yLCAwLjMzKSwgbGFiZWxzID0gTEVUVEVSU1sxOjNdLCBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywKICBwbG90X3BoeWNvYmlsaXNvbWUsCiAgcGxvdF9mbHZfZ2VuZXMsCiAgcGxvdF9jYXJib25fdXB0YWtlCikKZGV2Lm9mZigpCmBgYAoKQXMgYSBTdXBwbGVtZW50YXJ5IGZpZ3VyZSB0byBDKSwgd2UgY2FuICoqcGxvdCBhbGwgb3RoZXIgY2FyYm9uIHRyYW5zcG9ydGVycyBhbmQgcmVndWxhdG9yeSBnZW5lcyoqIHRoYXQgc2hvd2VkIGEgbGVzcyByZW1hcmthYmxlIGVmZmVjdC4KCmBgYHtyLCBmaWcud2lkdGggPSA2LCAgZmlnLmhlaWdodCA9IDIuNzV9CnBsb3RfY2FyYm9uX3VwdGFrZV8yIDwtIGRmX2dlbmUgJT4lIGZpbHRlcihsb2N1cyAlaW4lIGMoCiAgICAic2xsMDgzNCIsICJzbHIxNTEyIiwgInNscjE1MTMiLCAic2xsMTU5NCIsICJzbGwwMDMwIiwgInNsbDAzNTkiLCAic2xsMDgyMiIKICApKSAlPiUKICBtdXRhdGUoc2dSTkFfdGFyZ2V0ID0gcmVjb2RlKHNnUk5BX3RhcmdldCwKICAgIGBzbGwwODM0YCA9ICJiaWNBIiwgYHNscjE1MTJgID0gInNidEEiLCBgc2xyMTUxM2AgPSAic2J0QiIsCiAgICBgc2xsMDM1OWAgPSAiY3lhYnJCMSIsIGBzbGwwODIyYCA9ICJjeWFickIyIiwgYHJiY1JgID0gImNjbVIiCiAgKSkgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IGZhY3RvcihzZ1JOQV90YXJnZXQsIHVuaXF1ZShzZ1JOQV90YXJnZXQpKSkgJT4lCiAgcGxvdF9nZW5lX2ZpdG5lc3MobmNvbCA9IDQsIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCgpwbG90X2NhcmJvbl91cHRha2VfMgpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnN2ZyhmaWxlbmFtZSA9ICIuLi9maWd1cmVzL2ZpZ3VyZVMyLnN2ZyIsIHdpZHRoID0gNi4wLCBoZWlnaHQgPSAyLjc1KQpwcmludChwbG90X2NhcmJvbl91cHRha2VfMikKZGV2Lm9mZigpCmBgYAoKCkFzIGFub3RoZXIgU3VwcGxlbWVudGFyeSBGaWd1cmUsIHdlIGNhbiBwbG90IHRoZSAqKnRvdGFsIHByb3RlaW4gbWFzcyBvZiB0aGUgcGh5Y29iaWxpc29tZSoqIGRldGVybWluZWQgYnkgcHJvdGVpbiBtYXNzIHNwZWN0cm9tZXRyeS4KVGhpcyBkYXRhIHdhcyBwdWJsaXNoZWQgaW4gb3VyIHN0dWR5IFtKYWhuIGV0IGFsLiwgQ2VsbCBSZXBvcnRzLCAyMDE4XShodHRwczovL3d3dy5jZWxsLmNvbS9jZWxsLXJlcG9ydHMvZnVsbHRleHQvUzIyMTEtMTI0NygxOCkzMTQ4NS0yKS4gVGhlIGRhdGEgY2FuIGJlIGRvd25sb2FkZWQgZGlyZWN0bHkgZnJvbSB0aGUgU2hpbnlQcm90IGdpdGh1YiBwYWdlIHdoZXJlIGl0IGlzIGluY2x1ZGVkIGZvciBvbiBkZW1hbmQgdmlzdWFsaXphdGlvbi4KCmBgYHtyLCBmaWcud2lkdGggPSA2LCBmaWcuaGVpZ2h0ID0gMy42fQpsb2FkKHVybCgiaHR0cHM6Ly9naXRodWIuY29tL20tamFobi9TaGlueVByb3QvYmxvYi9tYXN0ZXIvZGF0YS9KYWhuXzIwMThfTGlnaHRfYW5kX0NPMl9saW0uUmRhdGE/cmF3PXRydWUiKSkKCnBsb3RfcHJvdG1hc3NfcGh5Y29iaWxpc29tZTEgPC0gSmFobl8yMDE4X0xpZ2h0X2FuZF9DTzJfbGltICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KHByb3RlaW4sICJbYWNdcGNbQUJDREVGR10iKSwgc2FtcGxlICE9ICJDTzIiKSAlPiUKICBtdXRhdGUocHJvdGVpbiA9IHN0cl9leHRyYWN0KHByb3RlaW4sICJbYWNdcGNbQUJDREVGR11bMTJdPyIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBmYWN0b3IobGlnaHQpLCB5ID0gMTAwKm1lYW5fbWFzc19mcmFjdGlvbl9ub3JtLCAKICBmaWxsID0gc3RyX3N1Yihwcm90ZWluLCAxLCAzKSwKICBsYWJlbCA9IGlmX2Vsc2Uoc3RyX2RldGVjdChwcm90ZWluLCAiW2FjXXBjW0MtWl1bMTJdPyIpLCAiIiwgcHJvdGVpbikpKSArCiAgbGltcyh5ID0gYygwLCAyMikpICsKICBnZW9tX2NvbChwb3NpdGlvbiA9ICJzdGFjayIsIHdpZHRoID0gMC43LCBjb2wgPSBncmV5KDEpLCBzaXplID0gMC4yKSArCiAgZ2VvbV90ZXh0KHNpemUgPSAyLjUsIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLCBjb2xvciA9ICJ3aGl0ZSIpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC41LCAiY20iKSkgKwogIGxhYnModGl0bGUgPSAiTGlnaHQgbGltaXRhdGlvbiIsIHggPSBleHByZXNzaW9uKCLCtW1vbCBwaG90b25zIG0iXi0yKiIgcyJeLTEpLCB5ID0gIiUgcHJvdGVpbiBtYXNzIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiM4ZWI2NTUiLCAiIzkzN2ZiMyIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiM4ZWI2NTUiLCAiIzkzN2ZiMyIpKQoKcGxvdF9wcm90bWFzc19waHljb2JpbGlzb21lMiA8LSBKYWhuXzIwMThfTGlnaHRfYW5kX0NPMl9saW0gJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QocHJvdGVpbiwgIlthY11wY1tBQkNERUZHXSIpLCBzYW1wbGUgPT0gIkNPMiIpICU+JQogIG11dGF0ZShwcm90ZWluID0gc3RyX2V4dHJhY3QocHJvdGVpbiwgIlthY11wY1tBQkNERUZHXVsxMl0/IikpICU+JQogIGdncGxvdChhZXMoeCA9IGZhY3RvcihjbzJfY29uY2VudHJhdGlvbiksIHkgPSAxMDAqbWVhbl9tYXNzX2ZyYWN0aW9uX25vcm0sIAogIGZpbGwgPSBzdHJfc3ViKHByb3RlaW4sIDEsIDMpLAogIGxhYmVsID0gaWZfZWxzZShzdHJfZGV0ZWN0KHByb3RlaW4sICJbYWNdcGNbQy1aXVsxMl0/IiksICIiLCBwcm90ZWluKSkpICsKICBsaW1zKHkgPSBjKDAsIDIyKSkgKwogIGdlb21fY29sKHBvc2l0aW9uID0gInN0YWNrIiwgd2lkdGggPSAwLjcsIGNvbCA9IGdyZXkoMSksIHNpemUgPSAwLjIpICsKICBnZW9tX3RleHQoc2l6ZSA9IDIuNSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksIGNvbG9yID0gIndoaXRlIikgKwogIGN1c3RvbV90aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjUsICJjbSIpKSArCiAgbGFicyh0aXRsZSA9ICJDTzIgbGltaXRhdGlvbiIsIHggPSAiJSBDTzIgaW4gYWlyIiwgeSA9ICIlIHByb3RlaW4gbWFzcyIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjOGViNjU1IiwgIiM5MzdmYjMiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjOGViNjU1IiwgIiM5MzdmYjMiKSkKCmdnYXJyYW5nZShuY29sID0gMiwgd2lkdGhzID0gYygwLjUsMC41KSwKICBsYWJlbHMgPSBMRVRURVJTWzE6Ml0sIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogIHBsb3RfcHJvdG1hc3NfcGh5Y29iaWxpc29tZTEsCiAgcGxvdF9wcm90bWFzc19waHljb2JpbGlzb21lMgopCmBgYAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kc3ZnKCIuLi9maWd1cmVzL2ZpZ3VyZVMxLnN2ZyIsIHdpZHRoID0gNiwgaGVpZ2h0ID0gMy42KQpnZ2FycmFuZ2UobmNvbCA9IDIsIHdpZHRocyA9IGMoMC41LDAuNSksCiAgbGFiZWxzID0gTEVUVEVSU1sxOjJdLCBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywKICBwbG90X3Byb3RtYXNzX3BoeWNvYmlsaXNvbWUxLAogIHBsb3RfcHJvdG1hc3NfcGh5Y29iaWxpc29tZTIKKQpkZXYub2ZmKCkKYGBgCgpPdGhlciBnZW5lcyBvZiBpbnRlcmVzdCB0aGF0IGVpdGhlciBkaWQgbm90IHNob3cgYW55IChyZW1hcmthYmxlKSBlZmZlY3Qgb24gZml0bmVzcywgb3IgZG8gbm90IG1lZXQgdGhlIHNjb3BlIG9mIHRoaXMgc2VjdGlvbjoKCi0gT0NQIChgc2xyMTk2M2ApLCBwZ3I1IChgc3NyMjAxNmApCi0gU2lnQiAoYHNsbDAzMDZgKSwgU2lnQyAoYHNsbDAxODRgKSwgU2lnRCAoYHNsbDIwMTJgKSwgU2lnRSAoYHNsbDE2ODlgKSAocnBvRCBnZW5lcyAxLTQpCi0gY2NtTSAoYHNsbDEwMzFgKSwgY2NtSzIgKGBzbGwxMDI4YCksIGNjbUsxIChgc2xsMTAyOWApLCBjY21OIChgc2xsMTAzMmApLCBjY21PIChgc2xyMDQzNmApLAogIGNjbUwgKGBzbGwxMDMwYCkKLSBDUDEyIChgc3NsMzM2NGApCgoKIyMgR2VuZXMgd2hlcmUga25vY2sgZG93biBsZWFkcyB0byBpbmNyZWFzZWQgZml0bmVzcwoKCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KbGlzdF9nZW5lc19wb3NfZml0bmVzcyA8LSBkZl9nZW5lICU+JQogIGZpbHRlcih0aW1lID09IDAsICFpcy5uYShsb2N1cyksIHdtZWFuX2ZpdG5lc3MgPiAyKSAlPiUKICBwdWxsKGxvY3VzKSAlPiUgdW5pcXVlCgpwbG90X2dlbmVfZml0bmVzcyhkZl9nZW5lLCBnZW5lID0gbGlzdF9nZW5lc19wb3NfZml0bmVzcywgdGl0bGUgPSAiR2VuZXMgd2l0aCBpbmNyZWFzZWQgZml0bmVzcyAoZiA+IDIpIikKYGBgCgpTdW1tYXJ5OgotIHBtZ0EgaXMgb25jZSBhZ2FpbiB0aGUgZ2VuZSB3aXRoIHN0cm9uZ2VzdCBhbmQgbW9zdCB3aWRlc3ByZWFkIGZpdG5lc3MgaW5jcmVhc2UsIHZhbGlkYXRpbmcgcmVzdWx0cyBmcm9tIGxpYnJhcnkgVjEKLSBzbHIxOTE2IHNhbWUgcGhlbm90eXBlIGFzIHBtZ0EganVzdCB3ZWFrZXIuIFdlIGFsc28ga25vdyB0aGlzIG9uZSBmcm9tIGJlZm9yZS4gTXVzdCBoYXZlIGlkZW50aWNhbCByb2xlIGFzIHBtZ0EuCi0gYWxsIFBTSUkgZ2VuZXMgc2hvdyBpbmNyZWFzZWQgZml0bmVzcyBpbiBwaG90b2hldGVyb3Ryb3BoaWMgY29uZGl0aW9uIC0tPiBQUyBpcyBhIGJ1cmRlbiBoZXJlCi0gc2xsMDY4OSwgcHhjQSwgc2xyMTYwOSAtIGFsbCBpbmNyZWFzZWQgZml0bmVzcyBpbiBIQyxITCwgZmlyc3QgdHdvIGFyZSBOYSsvQ08yICg/KSB0cm5hc3BvcnRlcnMsCiAgc2xyMTYwOSB3ZSBrbm93IGZyb20gYmVmb3JlLCAgIGFubm90YXRlZCBhcyBmYXR0eSBhY2lkIENvQSBsaWdhc2UsIGJ1dCBwcm9iYWJseSBpdCdzIHNvbWV0aGluZyBkaWZmZXJlbnQKLSBzbGw2MDU1LCBzbHIxNTA1LCBzbHIxOTkwIC0gYWxsIGluY3JlYXNlZCBmaXRuZXNzIGluIHBob3RvaGV0ZXJvdHJvcGhpYyBjb25kaXRpb24sIGFuZCBkZWNyZWFzZWQgZml0bmVzcyBpbiBIQy9MTCBjb25kaXRpb25zLgogIE5vdCBtdWNoIGlzIGtub3duIGFib3V0IHRoZXNlIGdlbmVzLCBwcm9iYWJseSBhIHJvbGUgaW4gcGhvdG9zeW50aGVzaXMsIGFzIHRoZSBwYXR0ZXJuIGlzIHNpbWlsYXIgdG8gcHNiIGdlbmVzIChQU0lJIG1hdHVyYXRpb24/KQotIHNscjA4MTMsIHNscjA5MDcsIHNscjkwOSwgc2xyMTI5OSAtIGFsbCBpbmNyZWFzZWQgZml0bmVzcyBpbiBIQy9MTC4gTm90IGNsZWFyIHdoYXQgY29ubmVjdHMgdGhlc2UgZ2VuZXMgZnVuY3Rpb25hbGx5LgoKCiMgRGlmZmVyZW50aWFsIGZpdG5lc3Mgb2Ygbm9uLWNvZGluZyBSTkFzIChuY1JOQXMpCgojIyBHZW5lcmFsIHRyZW5kcwoKVGhlIGZpcnN0IHRhc2sgdG8gc3R1ZHkgbmNSTkFzIGlzIHRvIGdlbmVyYXRlIGEgbmV3IGRhdGEgZnJhbWUgd2l0aCBhZGRpdGlvbmFsIGFubm90YXRpb24gZm9yIG5jUk5Bcy4KQWRkaXRpb25hbCBhbm5vdGF0aW9uIHRhYmxlcyB3ZXJlIGV4cG9ydGVkIGZyb20gR2VuZWlvdXMgYW5kIGFyZSBiYXNlZCBvbiB0aGUgcHVibGljYXRpb24gZnJvbSBbTWl0c2Noa2UgZXQgYWwuLCBQTkFTLCAyMDEwXShodHRwczovL2RvaS5vcmcvMTAuMTA3My9wbmFzLjEwMTUxNTQxMDgpLiBBY2NvcmRpbmcgdG8gdGhpcyBwdWJsaWNhdGlvbiwgbmNSTkFzIGFyZSBncm91cGVkIGludG8gZm91ciBkaWZmZXJlbnQgKHNsaWdodGx5IG92ZXJsYXBwaW5nKSBjbGFzc2VzOgoKLSBub24tY29kaW5nIHJlZ3VsYXRvcnkgUk5BcyAobmNSTkFzIGluIHN0cmljdCBzZW5zZSkgbm90IGFzc29jaWF0ZWQgdG8gYSBnZW5lCi0gaVRTUywgaW50ZXJuYWwgVFNTIHdpdGhpbiBhIGdlbmUKLSBhc1JOQSwgcmVndWxhdG9yeSBhbnRpLXNlbnNlIFJOQXMgYXNzb2NpYXRlZCB3aXRoIGEgZ2VuZQotIE5vdCBpbmNsdWRlZDogNSdVVFJzLCBhbHRlcm5hdGl2ZSB0cmFuc2NyaXB0aW9uIHN0YXJ0IHNpdGVzIChUU1MpIGFzc29jaWF0ZWQgdG8gYSBnZW5lCgpgYGB7cn0KZGZfbmNSTkEgPC0gZGZfbWFpbiAlPiUgZmlsdGVyKHNnUk5BX3R5cGUgPT0gIm5jUk5BIikgJT4lCiAgIyBvYnRhaW4gbnVtYmVyIG9mIHNnUk5BcyBwZXIgdGFyZ2V0CiAgZ3JvdXBfYnkoc2dSTkFfdGFyZ2V0KSAlPiUKICBzdW1tYXJpemUoc2dSTkFfbnVtYmVyID0gbGVuZ3RoKHVuaXF1ZShzZ1JOQV9wb3NpdGlvbikpKSAlPiUKICAjIG1lcmdlIHdpdGggZGZfZ2VuZSB0YWJsZQogIGlubmVyX2pvaW4oZGZfZ2VuZSwgYnkgPSAic2dSTkFfdGFyZ2V0IikgJT4lCiAgIyBnZW5lcmF0ZSBuY1JOQSB0eXBlIGJhc2VkIG9uIHRhcmdldCBuYW1lCiAgc2VsZWN0KC1sb2N1cywgLWdlbmVfbmFtZSwgLXNnUk5BX3R5cGUpICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSBzdHJfc3ViKHNnUk5BX3RhcmdldCwgNCwgMTAwMCkpICU+JQogIGxlZnRfam9pbihieSA9ICJzZ1JOQV90YXJnZXQiLAogICAgYmluZF9yb3dzKGxhcHBseShjKCJOQ18wMDA5MTFfbmNSTkEudHN2IiwgIk5DXzAwMDkxMV9hc1JOQS50c3YiLCAiTkNfMDAwOTExX2lUU1MudHN2IiksCiAgICAgIEZVTiA9IGZ1bmN0aW9uKGYpIHJlYWRfdHN2KHBhc3RlMCgiLi4vZGF0YS9pbnB1dC8iLCBmKSwgY29sX3R5cGVzID0gY29scygpKQogICAgKSkKICApCgpkZl9uY1JOQSAlPiUgZmlsdGVyKHRpbWUgPT0gMCkgJT4lCiAgZ3JvdXBfYnkobmNSTkFfdHlwZSwgY29uZGl0aW9uKSAlPiUKICBzZWxlY3QoY29uZGl0aW9uLCBzZ1JOQV90YXJnZXQsIG5jUk5BX3R5cGUsIGNvbWJfc2NvcmUpICU+JQogIHN1bW1hcml6ZSguZ3JvdXBzID0gImRyb3BfbGFzdCIsCiAgICBuX3RhcmdldHMgPSBsZW5ndGgodW5pcXVlKHNnUk5BX3RhcmdldCkpLAogICAgc2lnID0gc3VtKGNvbWJfc2NvcmUgPj0gNCksCiAgICBub25fc2lnID0gc3VtKGNvbWJfc2NvcmUgPCA0KSkgJT4lCiAgc3VtbWFyaXplKG5fdGFyZ2V0cyA9IG5fdGFyZ2V0c1sxXSwKICAgIGBzaWduaWZpY2FudCAoc2NvcmUgPj0gNClgID0gbWVhbihzaWcpLAogICAgYG5vbiBzaWduLiAoc2NvcmUgPCA0KWAgPSBuX3RhcmdldHMtYHNpZ25pZmljYW50IChzY29yZSA+PSA0KWAsCiAgICBgJSBzaWduLmAgPSBgc2lnbmlmaWNhbnQgKHNjb3JlID49IDQpYC9uX3RhcmdldHMqMTAwLAogICAgLmdyb3VwcyA9ICJkcm9wIikKYGBgCgpMb29raW5nIGF0IHRoZSBmaXRuZXNzIGFuZCBzaWduaWZpY2FuY2Ugc2NvcmVzIGZvciBvbmUgY29uZGl0aW9ucywgaXQgc2VlbXMgYXMgaW50ZXJuYWwgdHJhbnNjcmlwdGlvbiBzdGFydCBzaXRlcyBhcmUgb3ZlcnJlcHJlc2VudGVkIGluIHRoZSBncm91cCB0aGF0IHNob3dzIGFuIGVmZmVjdC4gVGhpcyBpcyBub3QgYSBzdXJwcmlzZSwgZ2l2ZW4gdGhhdCBzZ1JOQXMgdGFyZ2V0aW5nIGlUU1MgYmFzaWNhbGx5IGFsc28gcmVwcmVzcyB0aGUgbmF0aXZlIGdlbmUgYXMgYSByZWd1bGFyIHNnUk5BLiBUaGUgbGlicmFyeSBjb250YWlucyAxNzEyIG5jUk5BcyBlYWNoIHRhcmdldGVkIGJ5IDEgdG8gNSBzZ1JOQXMuIE9ubHkgdmVyeSBmZXcgb2YgdGhvc2Ugc2hvd2VkIGFuIGVmZmVjdCBvbiBmaXRuZXNzLgpXZSBjYW4gZmlsdGVyIGFsbCBuY1JOQXMgdGhhdCBoYXZlIGEgInNpZ25pZmljYW5jZSIgZXF1aXZhbGVudCB0byBhIGZpdG5lc3Mgc2NvcmUgYWJzKEYpID49IDIgYW5kIC1sb2cxMCBwLXZhbHVlID49IDIgKGFscGhhID0gMC4wMSkuClNpZ25pZmljYW5jZSBoZXJlIG1lYW5zIGVmZmVjdCBzaXplIChGKSBtdWx0aXBsaWVkIGJ5IC1sb2cxMCBwLXZhbHVlLCB0aGUgdGhyZXNob2xkIGlzIGluZGljYXRlZCBieSB0aGUgZGFzaGVkIGxpbmUuCgpgYGB7ciwgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodCA9IDIuNX0KcGxvdF9uY1JOQV9vdmVydmlldyA8LSBkZl9uY1JOQSAlPiUgZmlsdGVyKHRpbWUgPT0gMCwgY29uZGl0aW9uID09ICJIQywgSEwiKSAlPiUKICBtdXRhdGUobmNSTkFfdHlwZSA9IGZhY3RvcihuY1JOQV90eXBlLCBjKCJhc1JOQSIsICJpVFNTIiwgIm5jUk5BIikpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB3bWVhbl9maXRuZXNzLCB5ID0gLWxvZzEwKHBfZml0bmVzc19hZGopLCBjb2xvciA9IG5jUk5BX3R5cGUpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSwgc2l6ZSA9IDEuNSkgKwogIGdlb21fbGluZShkYXRhID0gZGF0YS5mcmFtZSh4ID0gYyhzZXEoLTgsIC0wLjUsIDAuMSksIHNlcSgwLjUsIDgsIDAuMSkpLAogICAgeSA9IDQvYyhzZXEoOCwgMC41LCAtMC4xKSwgc2VxKDAuNSwgOCwgMC4xKSkpLAogICAgYWVzKHggPSB4LCB5ID0geSwgc2hhcGUgPSBOVUxMLCBjb2wgPSBOVUxMKSwgbHR5ID0gMikgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygtNywgNyksIHlsaW0gPSBjKDAsIDMpKSArCiAgY3VzdG9tX3RoZW1lKGFzcGVjdCA9IDEsIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGZhY2V0X3dyYXAofiBuY1JOQV90eXBlLCBuY29sID0gMykgKwogIGxhYnMoeCA9ICJmaXRuZXNzIiwgeSA9IGV4cHJlc3Npb24oIi1sb2ciWzEwXSoiIHAtdmFsdWUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjdXN0b21fY29sb3JzKQoKcHJpbnQocGxvdF9uY1JOQV9vdmVydmlldykKYGBgCiMjIEFudGlzZW5zZSBSTkFzIGFuZCBpVFNTcwoKVGhlIGZpcnN0IHBhcnQgb2YgYSBtb3JlIGRldGFpbGVkIGFuYWx5c2lzIGlzIHRvIGV4dHJhY3QgYXNSTkFzIGFuZCBpVFNTcyB3aXRoIGRpZmZlcmVudGlhbCBmaXRuZXNzLCBhbmQgY29tcGFyZSB0aGVtIHRvIHRoZWlyIGFzc29jaWF0ZWQgZ2VuZXMuIFRoZSBhc3N1bXB0aW9uIGlzIHRoYXQgc2dSTkFzIHRhcmdldGluZyBhc1JOQXMvaVRTU3MgaW4gcmVhbGl0eSByZXByZXNzIHRyYW5zY3JpcHRpb24gb2YgdGhlaXIgcGFyZW50IGdlbmVzLCBhbmQgYnkgdGhlc2UgbWVhbnMgcHJvZHVjZSBhIGZpdG5lc3MgZWZmZWN0IHRoYXQgY2FuIG5vdCBiZSBhdHRyaWJ1dGVkIHRvIHRoZSBhY3Rpb24gb2YgdGhlIGFzUk5BIGl0c2VsZi4gVGhlIGZpcnN0IHN0ZXAgaXMgZmlsdGVyIHRoZSBuY1JOQSBkYXRhc2V0IGFuZCBvcmRlciBuY1JOQXMgYnkgZml0bmVzcyBzaW1pbGFyaXR5LiBUaGUgY29tcGxldGUgZmlndXJlIGZvciB0aGUgYW5hbHlzaXMgZm9sbG93cyBhdCB0aGUgZW5kIG9mIHRoZSBjaGFwdGVyLgoKYGBge3IsIGZpZy53aWR0aCA9IDMuNSwgZmlnLmhlaWdodCA9IDh9CmRmX25jUk5BX3NlbGVjdCA8LSBkZl9uY1JOQSAlPiUKICBmaWx0ZXIodGltZSA9PSAwKSAlPiUKICBncm91cF9ieShzZ1JOQV90YXJnZXQpICU+JQogIGZpbHRlcihhbnkoY29tYl9zY29yZSA+PSA0KSkgJT4lCiAgdW5ncm91cApgYGAKClBsb3QgYXNSTkEgdnMgZ2VuZSBmaXRuZXNzLgoKYGBge3J9CnBsb3RfYXNSTkFfeHkgPC0gZGZfbmNSTkFfc2VsZWN0ICU+JSBmaWx0ZXIobmNSTkFfdHlwZSA9PSAiYXNSTkEiKSAlPiUKICBsZWZ0X2pvaW4oYnkgPSBjKCJjb25kaXRpb24iLCAibG9jdXMiKSwKICAgIHNlbGVjdChkZl9nZW5lLCBsb2N1cywgY29uZGl0aW9uLCB3bWVhbl9maXRuZXNzLCBzZF9maXRuZXNzKSAlPiUgZGlzdGluY3QgJT4lCiAgICByZW5hbWUoZ2VuZV9maXRuZXNzID0gd21lYW5fZml0bmVzcywgc2RfZ2VuZV9maXRuZXNzID0gc2RfZml0bmVzcykpICU+JQogIHNlbGVjdChsb2N1cywgY29uZGl0aW9uLCB3bWVhbl9maXRuZXNzLCBnZW5lX2ZpdG5lc3MpICU+JQogIG11dGF0ZShsb2N1cyA9IGlmX2Vsc2UobG9jdXMgJWluJSBjKCJzbGwxNzczIiwgInNscjE2MDkiKSwgbG9jdXMsICJvdGhlciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB3bWVhbl9maXRuZXNzLCB5ID0gZ2VuZV9maXRuZXNzLCBjb2xvciA9IGxvY3VzKSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDQsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IC00LCBzbG9wZSA9IDEsIGx0eSA9IDIpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBzaXplID0gMS41KSArCiAgZ2dwdWJyOjpzdGF0X2NvcigpICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoLTksIDUpLCB5bGltID0gYygtOSwgNSkpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvcyA9IGMoMC44LCAwLjE1KSwgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjIsICJjbSIpKSArCiAgbGFicyh4ID0gImFzUk5BIGZpdG5lc3MiLCB5ID0gImdlbmUgZml0bmVzcyIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX2NvbG9yc1tjKDUsMToyKV0pCmBgYAoKUGxvdCBpVFNTIGZpdG5lc3MgdmVyc3VzIGdlbmUgZml0bmVzcy4KCmBgYHtyfQpwbG90X2lUU1NfeHkgPC0gZGZfbmNSTkFfc2VsZWN0ICU+JSBmaWx0ZXIobmNSTkFfdHlwZSA9PSAiaVRTUyIpICU+JQogIGxlZnRfam9pbihieSA9IGMoImNvbmRpdGlvbiIsICJsb2N1cyIpLAogICAgc2VsZWN0KGRmX2dlbmUsIGxvY3VzLCBjb25kaXRpb24sIHdtZWFuX2ZpdG5lc3MsIHNkX2ZpdG5lc3MpICU+JSBkaXN0aW5jdCAlPiUKICAgIHJlbmFtZShnZW5lX2ZpdG5lc3MgPSB3bWVhbl9maXRuZXNzLCBzZF9nZW5lX2ZpdG5lc3MgPSBzZF9maXRuZXNzKSkgJT4lCiAgc2VsZWN0KGxvY3VzLCBjb25kaXRpb24sIHdtZWFuX2ZpdG5lc3MsIGdlbmVfZml0bmVzcykgJT4lCiAgbXV0YXRlKGxvY3VzID0gaWZfZWxzZShsb2N1cyAlaW4lIGMoInNsbDE0MDYiLCAic2xyMTQxNSIsICJzbGwxNTQyIiwgInNsbDE1NTgiKSwgbG9jdXMsICJvdGhlciIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB3bWVhbl9maXRuZXNzLCB5ID0gZ2VuZV9maXRuZXNzLCBjb2xvciA9IGxvY3VzKSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDQsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IC00LCBzbG9wZSA9IDEsIGx0eSA9IDIpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBzaXplID0gMS41KSArCiAgZ2dwdWJyOjpzdGF0X2NvcigpICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoLTksIDUpLCB5bGltID0gYygtOSwgNSkpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvcyA9IGMoMC44LCAwLjE1KSwgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjIsICJjbSIpKSArCiAgbGFicyh4ID0gImlUU1MgZml0bmVzcyIsIHkgPSAiZ2VuZSBmaXRuZXNzIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjdXN0b21fY29sb3JzW2MoNSwxOjQpXSkKYGBgCgoKYGBge3IsIGZpZy53aWR0aCA9IDUuNSwgZmlnLmhlaWdodCA9IDUuNX0KZ2dhcnJhbmdlKG5yb3cgPSAyLCBsYWJlbHMgPSBjKCJBIiksIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogIHBsb3RfbmNSTkFfb3ZlcnZpZXcsCiAgZ2dhcnJhbmdlKG5jb2wgPSAyLCBsYWJlbHMgPSBjKCJCIiwgIkMiKSwgZm9udC5sYWJlbCA9IGxpc3RfZm9udHBhcnMsCiAgICBwbG90X2FzUk5BX3h5LCBwbG90X2lUU1NfeHkpCikKYGBgCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFLCBldmFsID0gVFJVRX0Kc3ZnKCIuLi9maWd1cmVzL2ZpZ3VyZVNYX2FzUk5BX2lUU1Muc3ZnIiwgd2lkdGggPSA1LjUsIGhlaWdodCA9IDUuNSkKZ2dhcnJhbmdlKG5yb3cgPSAyLCBsYWJlbHMgPSBjKCJBIiksIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogIHBsb3RfbmNSTkFfb3ZlcnZpZXcsCiAgZ2dhcnJhbmdlKG5jb2wgPSAyLCBsYWJlbHMgPSBjKCJCIiwgIkMiKSwgZm9udC5sYWJlbCA9IGxpc3RfZm9udHBhcnMsCiAgICBwbG90X2FzUk5BX3h5LCBwbG90X2lUU1NfeHkpCikKZGV2Lm9mZigpCmBgYAoKIyMgbm9uY29kaW5nIFJOQXMgYXMgcmVndWxhdG9yeSBlbGVtZW50cwoKVGhlIHNlY29uZCBwYXJ0IG9mIHRoaXMgYW5hbHlzaXMgaXMgdG8gbG9vayBhdCBub24tZ2VuZSBhc3NvY2lhdGVkIChpbnRlcmdlbmljKSBuY1JOQSBlbGVtZW50cy4gT2YgdGhlc2UsIHNldmVyYWwgYXJlIGtub3duIHRvIGhhdmUgYSByZWd1bGF0b3J5IGVmZmVjdC4gV2UgaW1wb3J0IGFuIGFsaWdubWVudCB0aGF0IHdhcyBkb25lIGluIGBweXRob25gIHVzaW5nIHRoZSBgYmlvcHl0aG9uYCBwYWNrYWdlLiBBbGwgbmNSTkFzIHdlcmUgYWxpZ25lZCB0byB0aGUgKlN5bmVjaG9jeXN0aXMqIGdlbm9tZSwgYW5kIHdlIGNhbiBub3cgZXh0cmFjdCB0aGUgZ2Vub21lIHBvc2l0aW9ucyB0byByZXRyaWV2ZSB0aGUgbmVpZ2hib3JpbmcgZ2VuZXMuCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFLCBldmFsID0gVFJVRX0KZ2V0X25laWdoYm9ycyA8LSBmdW5jdGlvbihuLCB0aHJlc2hvbGQsIHR5cGUgPSAibG9naWNhbCIsCiAgbG93ZXJfbmVpZ2hib3IgPSBUUlVFLCB1cHBlcl9uZWlnaGJvciA9IFRSVUUpIHsKICBuIDwtIHNvcnQobikKICBsIDwtIHJlcF9hbG9uZyhuLCBGQUxTRSkKICBmb3IgKHRyIGluIHRocmVzaG9sZCkgewogICAgaWYgKHRyID4gdGFpbChuLCAxKSB8IHRyIDwgblsxXSkKICAgICAgc3RvcCgidGhyZXNob2xkIGV4Y2VlZHMgcmFuZ2Ugb2YgaW5wdXQgdmFsdWVzIikKICAgIGlmICh1cHBlcl9uZWlnaGJvcikgewogICAgICBsIDwtIGwgfCAhZHVwbGljYXRlZCh0ciA8IG4pfQogICAgaWYgKGxvd2VyX25laWdoYm9yKSB7CiAgICAgIGwgPC0gbCB8ICFkdXBsaWNhdGVkKHRyIDwgbiwgZnJvbUxhc3QgPSBUUlVFKX0KICAgIGlmICghKHRyID49IG5bMV0gJiB0ciA8IG5bMl0pKSBsWzFdIDwtIEZBTFNFCiAgICBpZiAoISh0ciA+IG5bbGVuZ3RoKG4pLTFdICYgdHIgPD0gbltsZW5ndGgobildKSl7CiAgICAgIGxbbGVuZ3RoKG4pXSA8LSBGQUxTRX0gCiAgfQogIGlmICh0eXBlID09ICJsb2dpY2FsIikgcmV0dXJuKGwpCiAgZWxzZSBpZiAodHlwZSA9PSAicG9zIikgcmV0dXJuKHdoaWNoKGwpKQogIGVsc2UgaWYgKHR5cGUgPT0gInZhbHVlIikgcmV0dXJuKG5bbF0pCn0KYGBgCgpUaGUgZmlyc3Qgc3RlcCBpcyB0byBwYXJzZSB0aGUgKGltcG9ydGFudCkgaW5mb3JtYXRpb24gb2YgdGhlIGFsaWdubWVudCB0ZXh0IGZpbGUgaW50byBhIHRhYmxlLgoKYGBge3J9CmRmX2Fubm90YXRpb24gPC0gcmVhZF9jc3YoIi4uLy4uL3NnUk5BX2xpYnJhcnkvcmF3X2RhdGEvU3luZWNob2N5c3Rpc19QQ0M2ODAzX2dlbm9tZV9hbm5vdGF0aW9uXzIwMTkwNjE0LmNzdiIsIGNvbF90eXBlcyA9IGNvbHMoKSkgJT4lCiAgYXJyYW5nZShzdGFydF9icCkKCmRmX2FsaWdubWVudCA8LSByZWFkX2xpbmVzKCIuLi9kYXRhL291dHB1dC9uY1JOQV9hbGlnbm1lbnQudHh0IikgJT4lCiAgYXNfdGliYmxlICU+JQogIG11dGF0ZShuYW1lID0gcmVwKGMoImxvY3VzIiwgImdlbm9tZSIsICJnZW5vbWVfbGVuZ3RoIiwgInNjb3JlIiwgInNlcSIsICJhbGlnbm1lbnQiLCAiZ2Vub21lX3BvcyIsICJibGFuayIpLCBsZW5ndGgub3V0ID0gbigpKSkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9ICJuYW1lIiwgdmFsdWVzX2Zyb20gPSAidmFsdWUiKSAlPiUKICB0aWR5cjo6dW5jaG9wKGNvbHMgPSBldmVyeXRoaW5nKCkpICU+JQogIG11dGF0ZShsb2N1cyA9IHN0cl9yZW1vdmUobG9jdXMsICJhbGlnbm1lbnQgb2Y6ICIpKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChnZW5vbWUsICI0NzExODMwNCIpKSAlPiUKICBzZWxlY3QoLWdlbm9tZV9sZW5ndGgsIC1zY29yZSwgLXNlcSwgLWFsaWdubWVudCwgLWJsYW5rKSAlPiUKICBtdXRhdGUoCiAgICBnZW5vbWVfc3RhcnQgPSBzdHJfZXh0cmFjdChnZW5vbWVfcG9zLCAiU2JqY3Q6ICtbMC05XSoiKSAlPiUKICAgIHN0cl9leHRyYWN0KCJbMC05XSsiKSAlPiUgYXMubnVtZXJpYykgJT4lCiAgbXV0YXRlKAogICAgZ2Vub21lX2VuZCA9IHN0cl9leHRyYWN0KGdlbm9tZV9wb3MsICJbQVRDR10gK1swLTldKiIpICU+JQogICAgc3RyX2V4dHJhY3QoIlswLTldKyIpICU+JSBhcy5udW1lcmljKSAlPiUKICBtdXRhdGUobGVuZ3RoID0gYWJzKGdlbm9tZV9lbmQtZ2Vub21lX3N0YXJ0KSkKCmhlYWQoZGZfYWxpZ25tZW50KQpgYGAKVGhlIHNlY29uZCBzdGVwIGlzIHRvIGxvb2sgdXAgdGhlIDUnIGFuZCAzJyBuZWlnaGJvcmluZyBnZW5lcyBmb3IgZWFjaCBuY1JOQS4gT25lIGNvdWxkIGFsc28gZG8gdGhpcyBtYW51YWxseSBidXQgaXQgYmVjb21lcyBpbXByYWN0aWNhbCBmb3Igc29tZXRoaW5nIGxpa2UgMTArIG5jUk5BIGxvY2kuIEFmdGVyIG9idGFpbmluZyBhIHRhYmxlIHdpdGggYm90aCB0aGUgbmNSTkEgaW4gb25lIGNvbHVtbiBhbmQgaXRzIGFzc29jaWF0ZWQgNScgYW5kIDMnIG5laWdoYm9yaW5nIGdlbmVzIGluIHNlcGFyYXRlIGNvbHVtbnMsIHdlIGNhbiBtYWtlIGEgc3VtbWFyeSBmaXRuZXNzIHRhYmxlIGFuZCBwbG90IGZpdG5lc3Mgb2YgbmNSTkEgYW5kIGl0cyBuZWlnaGJvcnMgYWdhaW5zdCBlYWNoIG90aGVyLgoKYGBge3J9CiMgZ2V0IG5laWdoYm9yaW5nIGdlbmVzCmRmX2FsaWdubWVudCA8LSBkZl9hbGlnbm1lbnQgJT4lCiAgYmluZF9jb2xzKAogICAgc2FwcGx5KGRmX2FsaWdubWVudCRnZW5vbWVfc3RhcnQsIGZ1bmN0aW9uKHgpIHsKICAgICAgZGYgPSBmaWx0ZXIoZGZfYW5ub3RhdGlvbiwgbG9jYXRpb24gPT0gIkNociIpCiAgICAgIG5laWdoYm9ycyA9IGdldF9uZWlnaGJvcnMobiA9IGRmJHN0YXJ0X2JwLCB0aHJlc2hvbGQgPSB4LCB0eXBlID0gInBvcyIpCiAgICAgIHVubGlzdChhcy5saXN0KGRmW25laWdoYm9ycywgIkdlbmVJRCJdKSkKICAgIH0pICU+JSB0ICU+JSBhc190aWJibGUKICApCgojIHNvbWUgam9pbiBvcGVyYXRpb25zIHRvIG1lcmdlIGRpZmZlcmVudCBmaXRuZXNzIGRhdGEgaW4gb25lIERGCmRmX25jUk5BX2NvbXAgPC0gZGZfbmNSTkFfc2VsZWN0ICU+JSBmaWx0ZXIobmNSTkFfdHlwZSA9PSAibmNSTkEiKSAlPiUKICBsZWZ0X2pvaW4ocmVuYW1lKGRmX2FsaWdubWVudCwgc2dSTkFfdGFyZ2V0ID0gbG9jdXMsIHVwc3RyZWFtID0gR2VuZUlEMSwgZG93bnN0cmVhbSA9IEdlbmVJRDIpICU+JQogICAgc2VsZWN0KHNnUk5BX3RhcmdldCwgdXBzdHJlYW0sIGRvd25zdHJlYW0pLAogICAgYnkgPSAic2dSTkFfdGFyZ2V0IikgJT4lCiAgbGVmdF9qb2luKGRmX2dlbmUgJT4lIHNlbGVjdChsb2N1cywgY29uZGl0aW9uLCB3bWVhbl9maXRuZXNzKSAlPiUKICAgIGRpc3RpbmN0ICU+JSByZW5hbWUodXBzdHJlYW0gPSBsb2N1cywgd21lYW5fZml0bmVzc191cyA9IHdtZWFuX2ZpdG5lc3MpLAogICAgYnkgPSBjKCJjb25kaXRpb24iLCAidXBzdHJlYW0iKQogICkgJT4lCiAgbGVmdF9qb2luKGRmX2dlbmUgJT4lIHNlbGVjdChsb2N1cywgY29uZGl0aW9uLCB3bWVhbl9maXRuZXNzKSAlPiUKICAgIGRpc3RpbmN0ICU+JSByZW5hbWUoZG93bnN0cmVhbSA9IGxvY3VzLCB3bWVhbl9maXRuZXNzX2RzID0gd21lYW5fZml0bmVzcyksCiAgICBieSA9IGMoImNvbmRpdGlvbiIsICJkb3duc3RyZWFtIikKICApCgojIGdldCBhIGxpc3Qgb2YgaW50ZXJlc3RpbmcgbmNSTkFzIHdoZXJlIHRoZSBuZWlnaGJvcmluZyBnZW5lCiMgaGFzIE5PIGVmZmVjdCBvbiBmaXRuZXNzCmxpc3RfbmNSTkFfaGl0cyA8LSBkZl9uY1JOQV9jb21wICU+JQogIGZpbHRlcigKICAgIGFicyh3bWVhbl9maXRuZXNzIC0gd21lYW5fZml0bmVzc191cykgPj0gNCAmCiAgICBhYnMod21lYW5fZml0bmVzcyAtIHdtZWFuX2ZpdG5lc3NfZHMpID49IDQKICApICU+JSBncm91cF9ieShzZ1JOQV90YXJnZXQpICU+JSBjb3VudCAlPiUKICBmaWx0ZXIobiA+IDEpICU+JQogIHB1bGwoc2dSTkFfdGFyZ2V0KQpgYGAKClRoZSB0aGlyZCBzdGVwIGlzIHRvIHBsb3QgYSBoZWF0IG1hcCBhbmQgdGhlIGNvbXBhcmlzb24gb2YgdXBzdHJlYW0gYW5kIGRvd25zdHJlYW0gZ2VuZSBmaXRuZXNzLgoKYGBge3J9CnBsb3RfbmNSTkFfaGVhdCA8LSBkZl9uY1JOQV9zZWxlY3QgJT4lIGZpbHRlcihuY1JOQV90eXBlID09ICJuY1JOQSIpICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSBmY3RfY2x1c3RlcihzZ1JOQV90YXJnZXQsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcykpICU+JQogIG11dGF0ZSh3bWVhbl9maXRuZXNzID0gd21lYW5fZml0bmVzcyAlPiUgcmVwbGFjZSguLCAuID4gNCwgNCkgJT4lIHJlcGxhY2UoLiwgLiA8IC00LCAtNCkpICU+JQogIGdncGxvdChhZXMoeCA9IGNvbmRpdGlvbiwgeSA9IHNnUk5BX3RhcmdldCwgZmlsbCA9IHdtZWFuX2ZpdG5lc3MpKSArCiAgZ2VvbV90aWxlKCkgKyBjdXN0b21fdGhlbWUobGVnZW5kLnBvcyA9ICJib3R0b20iLCBsZWdlbmQua2V5LnNpemUgPSB1bml0KDAuNCwgImNtIiksIGxlZ2VuZC5tYXJnaW4gPSB1bml0KDAsICJjbSIpKSArCiAgbGFicyh4ID0gIiIsIHkgPSAiIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdCA9IDEpKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IGMoY3VzdG9tX2NvbG9yc1sxXSwgZ3JleSgwLjkpLCBjdXN0b21fY29sb3JzWzJdKSwKICAgIGxpbWl0cyA9IGMoLTQsIDQpKQoKIyBjaGVjayBjb3JyZWxhdGlvbiBvZiBuY1JOQSBmaXRuZXNzIHdpdGggdXBzdHJlYW0gbHlpbmcgZ2VuZXMKcGxvdF9uY1JOQV91cHN0cmVhbSA8LSBkZl9uY1JOQV9jb21wICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSBpZl9lbHNlKGxvY3VzICVpbiUgbGlzdF9uY1JOQV9oaXRzLCBzZ1JOQV90YXJnZXQsICJvdGhlciIpKSAlPiUKICBhcnJhbmdlKGRlc2Moc2dSTkFfdGFyZ2V0KSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gd21lYW5fZml0bmVzcywgeSA9IHdtZWFuX2ZpdG5lc3NfdXMsIGNvbG9yID0gc2dSTkFfdGFyZ2V0KSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDQsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IC00LCBzbG9wZSA9IDEsIGx0eSA9IDIpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBzaXplID0gMS41KSArCiAgZ2dwdWJyOjpzdGF0X2NvcigpICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoLTksIDUpLCB5bGltID0gYygtOSwgNSkpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvcyA9IGMoMC44LCAwLjIpLCBsZWdlbmQua2V5LnNpemUgPSB1bml0KDAuMiwgImNtIikpICsKICBsYWJzKHggPSAibmNSTkEgZml0bmVzcyIsIHkgPSAidXBzdHJlYW0gZ2VuZSBmaXRuZXNzIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjdXN0b21fY29sb3JzW2MoMToyLDUpXSkKCiMgY2hlY2sgY29ycmVsYXRpb24gb2YgbmNSTkEgZml0bmVzcyB3aXRoIGRvd25zdHJlYW0gbHlpbmcgZ2VuZXMKcGxvdF9uY1JOQV9kb3duc3RyZWFtIDwtIGRmX25jUk5BX2NvbXAgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IGlmX2Vsc2UobG9jdXMgJWluJSBsaXN0X25jUk5BX2hpdHMsIHNnUk5BX3RhcmdldCwgIm90aGVyIikpICU+JQogIGFycmFuZ2UoZGVzYyhzZ1JOQV90YXJnZXQpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB3bWVhbl9maXRuZXNzLCB5ID0gd21lYW5fZml0bmVzc19kcywgY29sb3IgPSBzZ1JOQV90YXJnZXQpKSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBsdHkgPSAyKSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gNCwgc2xvcGUgPSAxLCBsdHkgPSAyKSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gLTQsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUsIHNpemUgPSAxLjUpICsKICBnZ3B1YnI6OnN0YXRfY29yKCkgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygtOSwgNSksIHlsaW0gPSBjKC05LCA1KSkgKwogIGN1c3RvbV90aGVtZShsZWdlbmQucG9zID0gYygwLjgsIDAuMiksIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC4yLCAiY20iKSkgKwogIGxhYnMoeCA9ICJuY1JOQSBmaXRuZXNzIiwgeSA9ICJkb3duc3RyZWFtIGdlbmUgZml0bmVzcyIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX2NvbG9yc1tjKDE6Miw1KV0pCmBgYAoKCk1haW4gdGV4dCBmaWd1cmUgZm9yIG5jUk5BcyBvbmx5LgoKYGBge3IsIGZpZy53aWR0aCA9IDUuNSwgZmlnLmhlaWdodCA9IDYuNX0KZ2dhcnJhbmdlKG5jb2wgPSAyLCBsYWJlbHMgPSBjKCJBIiwgIkIiKSwgZm9udC5sYWJlbCA9IGxpc3RfZm9udHBhcnMsCiAgcGxvdF9uY1JOQV9oZWF0ICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCAtMjAsIDEyKSwgInBvaW50cyIpKSwKICBnZ2FycmFuZ2UobnJvdyA9IDMsIGhlaWdodHMgPSBjKDAuNDMsIDAuNDMsIDAuMTQpLAogICAgbGFiZWxzID0gYygiIiwgIkMiLCAiIiksIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogICAgcGxvdF9uY1JOQV91cHN0cmVhbSwKICAgIHBsb3RfbmNSTkFfZG93bnN0cmVhbQogICkKKQpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0UsIGV2YWwgPSBUUlVFfQpzdmcoIi4uL2ZpZ3VyZXMvZmlndXJlNS5zdmciLCB3aWR0aCA9IDUuNSwgaGVpZ2h0ID0gNi41KQpnZ2FycmFuZ2UobmNvbCA9IDIsIGxhYmVscyA9IGMoIkEiLCAiQiIpLCBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywKICBwbG90X25jUk5BX2hlYXQgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxMiwgMTIsIC0yMCwgMTIpLCAicG9pbnRzIikpLAogIGdnYXJyYW5nZShucm93ID0gMywgaGVpZ2h0cyA9IGMoMC40MywgMC40MywgMC4xNCksCiAgICBsYWJlbHMgPSBjKCIiLCAiQyIsICIiKSwgZm9udC5sYWJlbCA9IGxpc3RfZm9udHBhcnMsCiAgICBwbG90X25jUk5BX3Vwc3RyZWFtLAogICAgcGxvdF9uY1JOQV9kb3duc3RyZWFtCiAgKQopCmRldi5vZmYoKQpgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFfQpkZl9uY1JOQV9zZWxlY3QgJT4lIGZpbHRlcihuY1JOQV90eXBlID09ICJuY1JOQSIpICU+JQogIHNlbGVjdCghbWF0Y2hlcygidGltZXxsb2cyRm9sZCIpKSAlPiUKICB3cml0ZV9jc3YoIi4uL2RhdGEvb3V0cHV0L2ZpdG5lc3NfbmNSTkEuY3N2IikKYGBgCgoKIyBFeHBvcnQgc3VtbWFyeSB0YWJsZSBvZiBhbGwgZ2VuZXMgYW5kIGNvbmRpdGlvbnMKCkV4cG9ydCBhIHN1bW1hcnkgdGFibGUgb2YgYWxsIGdlbmVzIGFuZCBjb25kaXRpb25zLCBzbyB0aGF0IGl0J3MgZWFzeSBmb3Igb3RoZXIgcGVvcGxlIHRvIGxvb2sgdXAgc2luZ2xlIGNvbmRpdGlvbnMgYXMgZm9yIGV4YW1wbGUgZG9uZSBpbiBbb25lLWJ5LW9uZSBmaXRuZXNzIGNvbXBhcmlzb25zXSgjZml0bmVzcy1vZi1hbGwtY29uZGl0aW9ucy12cy1lYWNoLW90aGVyKS4gVGhpcyBpcyBiZXN0IGRvbmUgaW4gd2lkZSBmb3JtYXQgKG9uZSBjb2x1bW4gcGVyIGNvbmRpdGlvbikuCgpgYGB7cn0KZGZfZ2VuZSAlPiUgdW5ncm91cCAlPiUKICBmaWx0ZXIoc2dSTkFfdHlwZSA9PSAiZ2VuZSIpICU+JQogIHNlbGVjdChsb2N1cywgc2dSTkFfdGFyZ2V0LCBnZW5lX25hbWUsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcykgJT4lIAogIGRpc3RpbmN0ICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBjb25kaXRpb24sIHZhbHVlc19mcm9tID0gd21lYW5fZml0bmVzcykgJT4lCiAgd3JpdGVfY3N2KCIuLi9kYXRhL291dHB1dC9maXRuZXNzX3N1bW1hcnkuY3N2IikKCmRmX2dlbmUgJT4lCiAgZmlsdGVyKHNnUk5BX3R5cGUgPT0gImdlbmUiKSAlPiUKICB3cml0ZV9jc3YoIi4uL2RhdGEvb3V0cHV0L2ZpdG5lc3NfZ2VuZXMuY3N2IikKCmRmX2tlZ2cgJT4lIHdyaXRlX2NzdigiLi4vZGF0YS9vdXRwdXQva2VnZ19hbm5vdGF0aW9uLmNzdiIpCmBgYAoKVGhlIGVudGlyZSBwaXBlbGluZSB0YWtlcyBhYm91dCAyNSBtaW51dGVzIHRvIHJ1biBvbiBhIHN0YW5kYXJkIG5vdGVib29rLgpUbyB3b3JrIG9uIHNpbmdsZSBzZWN0aW9ucywgdGhlIHdvcmsgc3BhY2UgaXMgZXhwb3J0ZWQgdG8gYXZvaWQgY29uc3RhbnQgcmVjYWxjdWxhdGlvbiBvZiByZXN1bHQgdGFibGVzLgoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRSwgZWNobyA9IEZBTFNFLCBldmFsID0gRkFMU0V9CiMgdGhpcyBoaWRkZW4gY29kZSBjaHVuayBzaW1wbHkgZXhwb3J0cyB0aGUgZml0bmVzcyBkYXRhIGluIGEgZGlmZmVyZW50IGZvcm1hdAojIHN1aXRhYmxlIGZvciBkZXBvc2l0aW9uIG9uIHRoZSBTaGlueUxpYiBhcHAgCiMgQXBwOiBodHRwczovL20tamFobi5zaGlueWFwcHMuaW8vU2hpbnlMaWIvCiMgR2l0aHViOiBodHRwczovL2dpdGh1Yi5jb20vbS1qYWhuL1NoaW55TGliCkNSSVNQUmlfbGlicmFyeV8yMDIyIDwtIGRmX21haW4gJT4lIHVuZ3JvdXAgJT4lCiAgZmlsdGVyKHNnUk5BX3R5cGUgPT0gImdlbmUiKSAlPiUKICBzZWxlY3QoLWdyb3VwLCAtcmVmZXJlbmNlX2dyb3VwLCAtYmFzZU1lYW4sIC1wcm90ZWluLCAtbGVuZ3RoLCAtbWFzcywgLWVjX251bWJlcikgJT4lCiAgbGVmdF9qb2luKGJ5ID0gImxvY3VzIiwgZGZfYW5ub3RhdGlvbiAlPiUKICAgIHNlbGVjdChHZW5lSUQsIFByb2Nlc3MsIFBhdGh3YXksIFByb3RlaW4pICU+JQogICAgcmVuYW1lKGxvY3VzID0gR2VuZUlELCBwcm9jZXNzID0gUHJvY2VzcywgcGF0aHdheSA9IFBhdGh3YXksIHByb3RlaW4gPSBQcm90ZWluKSkgJT4lCiAgbXV0YXRlKEZvbGRDaGFuZ2UgPSAyXmxvZzJGb2xkQ2hhbmdlKQoKIyBzYXZlIGFzIFJkYXRhIGZpbGUKc2F2ZShDUklTUFJpX2xpYnJhcnlfMjAyMiwgZmlsZSA9ICIuLi8uLi8uLi9TaGlueUxpYi9kYXRhL0NSSVNQUmlfbGlicmFyeV8yMDIyLlJkYXRhIikKCiMgc2F2ZSBhcyBjc3YgZmlsZSBmb3IgTUwgYXBwbGljYXRpb24KQ1JJU1BSaV9saWJyYXJ5XzIwMjIgJT4lIHNlbGVjdCgKICBzZ1JOQSwgc2dSTkFfdGFyZ2V0LCBzZ1JOQV9wb3NpdGlvbiwgY29uZGl0aW9uLAogIHRpbWUsIGxvZzJGb2xkQ2hhbmdlLCBmaXRuZXNzLCBzZ1JOQV9pbmRleCwgc2dSTkFfdHlwZSwgbG9jdXMsCiAgc2dSTkFfY29ycmVsYXRpb24sIHNnUk5BX2VmZmljaWVuY3kpICU+JQogIHdyaXRlX2NzdihmaWxlID0gIi4uL2RhdGEvb3V0cHV0L2ZpdG5lc3Nfc2dSTkEuY3N2IikKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KIyByZW1vdmUgbGFyZ2UgaW50ZXJtZWRpYXRlIG9iamVjdHMKaWYgKCJsaXN0X2NvbmRpdGlvbl9wYWlycyIgJWluJSBscygpKSBybSgibGlzdF9jb25kaXRpb25fcGFpcnMiKQoKIyBleHBvcnQgd29ya3NwYWNlCnNhdmUobGlzdCA9IGxzKCksIGZpbGUgPSAiLi4vcGlwZWxpbmUvQ1JJU1BSaV9WMl9kYXRhX3Byb2Nlc3NpbmcuUmRhdGEiKQpgYGAKCgojIFNlc3Npb24gSW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgo=